diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 54774c1d7a..67a994adf6 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -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 }} diff --git a/.github/workflows/docs-netlify.yml b/.github/workflows/docs-netlify.yml new file mode 100644 index 0000000000..ce0f40d640 --- /dev/null +++ b/.github/workflows/docs-netlify.yml @@ -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 }} \ No newline at end of file diff --git a/.github/workflows/netlify.yml b/.github/workflows/netlify.yml deleted file mode 100644 index a5fc237744..0000000000 --- a/.github/workflows/netlify.yml +++ /dev/null @@ -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 }} diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index ead9ba50bf..203ee88150 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -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": [], diff --git a/.github/workflows/tooljet-release-docker-image-build.yml b/.github/workflows/tooljet-release-docker-image-build.yml deleted file mode 100644 index 131d33c619..0000000000 --- a/.github/workflows/tooljet-release-docker-image-build.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/vulnerability-ci.yml b/.github/workflows/vulnerability-ci.yml new file mode 100644 index 0000000000..15f8425a46 --- /dev/null +++ b/.github/workflows/vulnerability-ci.yml @@ -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 }}). \ No newline at end of file diff --git a/.version b/.version index 7c69a55dbb..afad818663 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.7.0 +3.11.0 diff --git a/cypress-tests/cypress-app-builder.config.js b/cypress-tests/cypress-app-builder.config.js index 5d291f59c1..7dc482d59d 100644 --- a/cypress-tests/cypress-app-builder.config.js +++ b/cypress-tests/cypress-app-builder.config.js @@ -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); }, diff --git a/cypress-tests/cypress-marketplace.config.js b/cypress-tests/cypress-marketplace.config.js index 7536a87482..b8ffbeaa26 100644 --- a/cypress-tests/cypress-marketplace.config.js +++ b/cypress-tests/cypress-marketplace.config.js @@ -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); }, diff --git a/cypress-tests/cypress-platform.config.js b/cypress-tests/cypress-platform.config.js index 465c3c6c0e..2ae3bec971 100644 --- a/cypress-tests/cypress-platform.config.js +++ b/cypress-tests/cypress-platform.config.js @@ -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); }, diff --git a/cypress-tests/cypress-run.config.js b/cypress-tests/cypress-run.config.js index 7a09e7218a..10445c10b2 100644 --- a/cypress-tests/cypress-run.config.js +++ b/cypress-tests/cypress-run.config.js @@ -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); }, diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 5fa2e2a56d..319063995e 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -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, diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 0a0dc58e3e..18e66a84b6 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -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; + }); + }); +}); diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index 5df60a6c5a..1bf26407c7 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -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) => { diff --git a/cypress-tests/cypress/constants/selectors/dataSource.js b/cypress-tests/cypress/constants/selectors/dataSource.js index 8283eb2b92..242747e8b1 100644 --- a/cypress-tests/cypress/constants/selectors/dataSource.js +++ b/cypress-tests/cypress/constants/selectors/dataSource.js @@ -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"]` }, }; diff --git a/cypress-tests/cypress/constants/selectors/manageGroups.js b/cypress-tests/cypress/constants/selectors/manageGroups.js index 1a83a90ed2..26ae4f3219 100644 --- a/cypress-tests/cypress/constants/selectors/manageGroups.js +++ b/cypress-tests/cypress/constants/selectors/manageGroups.js @@ -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"]', diff --git a/cypress-tests/cypress/constants/texts/common.js b/cypress-tests/cypress/constants/texts/common.js index 5447e20bd9..bc3f566129 100644 --- a/cypress-tests/cypress/constants/texts/common.js +++ b/cypress-tests/cypress/constants/texts/common.js @@ -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", diff --git a/cypress-tests/cypress/constants/texts/exportImport.js b/cypress-tests/cypress/constants/texts/exportImport.js index 1a405efa77..398cb2f2c4 100644 --- a/cypress-tests/cypress/constants/texts/exportImport.js +++ b/cypress-tests/cypress/constants/texts/exportImport.js @@ -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", diff --git a/cypress-tests/cypress/constants/texts/manageGroups.js b/cypress-tests/cypress/constants/texts/manageGroups.js index 0b06097b3e..e20dbd87e1 100644 --- a/cypress-tests/cypress/constants/texts/manageGroups.js +++ b/cypress-tests/cypress/constants/texts/manageGroups.js @@ -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", diff --git a/cypress-tests/cypress/constants/texts/manageUsers.js b/cypress-tests/cypress/constants/texts/manageUsers.js index 3e463c685a..a7b9f9abd0 100644 --- a/cypress-tests/cypress/constants/texts/manageUsers.js +++ b/cypress-tests/cypress/constants/texts/manageUsers.js @@ -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 won’t 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", }; diff --git a/cypress-tests/cypress/constants/texts/version.js b/cypress-tests/cypress/constants/texts/version.js index e6eb855f49..4f640db63a 100644 --- a/cypress-tests/cypress/constants/texts/version.js +++ b/cypress-tests/cypress/constants/texts/version.js @@ -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`; diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImportAndExport.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImportAndExport.cy.js index fa8f5e9918..b5271aa8d7 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImportAndExport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImportAndExport.cy.js @@ -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") diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js index d23e9d53e8..1d6c45b516 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js @@ -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", () => { diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js index 196113f2d2..75c1cb4b0d 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js @@ -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", () => { diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js index d03860574d..ff992d4ddc 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js @@ -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" }] diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js index c985afbfaa..2c8fd53c8c 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js @@ -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", [ diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js index bf289d7cb9..ebf671e667 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Login.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Login.cy.js index bc3d5f3894..55688c9222 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Login.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Login.cy.js @@ -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); }); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Signup.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Signup.cy.js index 248562f9dc..40bc083798 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Signup.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/Signup.cy.js @@ -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); }); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/UserInviteFlow.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/UserInviteFlow.cy.js index 0851933e1a..4d737c64ea 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/UserInviteFlow.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/UserInviteFlow.cy.js @@ -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(); - }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/resetPassword.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/resetPassword.cy.js index 7805bec5bf..caa67b2544 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/resetPassword.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/resetPassword.cy.js @@ -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) => { diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/userInviteFlowEdgeCases.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/userInviteFlowEdgeCases.cy.js new file mode 100644 index 0000000000..da491e39a9 --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/userManagment/userInviteFlowEdgeCases.cy.js @@ -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(); + }); +}); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.cy.js index 1a40c978c8..4a11a1dcba 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.cy.js @@ -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", diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js index e109c3d4ab..101d9ffc9c 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js @@ -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"); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js index 132ddae9b2..d69cf77689 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/manageGroups.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/manageGroups.cy.js index 966cbcd930..1d43828b67 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/manageGroups.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/manageGroups.cy.js @@ -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(); }; diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js index 8fefd2bb5c..bdf3593e5c 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js @@ -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", [ diff --git a/cypress-tests/cypress/support/utils/apps.js b/cypress-tests/cypress/support/utils/apps.js index 814c12cc25..49e8c841b0 100644 --- a/cypress-tests/cypress/support/utils/apps.js +++ b/cypress-tests/cypress/support/utils/apps.js @@ -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]; }; - diff --git a/cypress-tests/cypress/support/utils/common.js b/cypress-tests/cypress/support/utils/common.js index eaf5bdb7bc..e7fab862cc 100644 --- a/cypress-tests/cypress/support/utils/common.js +++ b/cypress-tests/cypress/support/utils/common.js @@ -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 { diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js index f6e63cadb3..e907a991e2 100644 --- a/cypress-tests/cypress/support/utils/dataSource.js +++ b/cypress-tests/cypress/support/utils/dataSource.js @@ -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) => { diff --git a/cypress-tests/cypress/support/utils/manageGroups.js b/cypress-tests/cypress/support/utils/manageGroups.js index 2c114dfad8..b6b67ca629 100644 --- a/cypress-tests/cypress/support/utils/manageGroups.js +++ b/cypress-tests/cypress/support/utils/manageGroups.js @@ -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); diff --git a/cypress-tests/cypress/support/utils/manageSSO.js b/cypress-tests/cypress/support/utils/manageSSO.js index 4a1a41fecb..4fc76774a8 100644 --- a/cypress-tests/cypress/support/utils/manageSSO.js +++ b/cypress-tests/cypress/support/utils/manageSSO.js @@ -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}'`) diff --git a/cypress-tests/cypress/support/utils/manageUsers.js b/cypress-tests/cypress/support/utils/manageUsers.js index 6bf47b8868..b3f16c70fc 100644 --- a/cypress-tests/cypress/support/utils/manageUsers.js +++ b/cypress-tests/cypress/support/utils/manageUsers.js @@ -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"); }; diff --git a/cypress-tests/cypress/support/utils/onboarding.js b/cypress-tests/cypress/support/utils/onboarding.js index 44ab148934..182e890acd 100644 --- a/cypress-tests/cypress/support/utils/onboarding.js +++ b/cypress-tests/cypress/support/utils/onboarding.js @@ -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"); }); -}; \ No newline at end of file +}; + +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(); +}; diff --git a/cypress-tests/cypress/support/utils/postgreSql.js b/cypress-tests/cypress/support/utils/postgreSql.js index 227e12efa7..b6f3f827db 100644 --- a/cypress-tests/cypress/support/utils/postgreSql.js +++ b/cypress-tests/cypress/support/utils/postgreSql.js @@ -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 = () => { }; \ No newline at end of file diff --git a/cypress-tests/cypress/support/utils/version.js b/cypress-tests/cypress/support/utils/version.js index 9d3bdb5043..77b7d8b0e8 100644 --- a/cypress-tests/cypress/support/utils/version.js +++ b/cypress-tests/cypress/support/utils/version.js @@ -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(); +}; diff --git a/frontend/.version b/frontend/.version index 7c69a55dbb..afad818663 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.7.0 +3.11.0 diff --git a/frontend/assets/images/icons/widgets/emailinput.jsx b/frontend/assets/images/icons/widgets/emailinput.jsx new file mode 100644 index 0000000000..1394959b50 --- /dev/null +++ b/frontend/assets/images/icons/widgets/emailinput.jsx @@ -0,0 +1,39 @@ +import React from 'react'; + +const EmailInput = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => ( + + + + + + +); + +export default EmailInput; diff --git a/frontend/assets/images/icons/widgets/horizontalDivider.jsx b/frontend/assets/images/icons/widgets/horizontalDivider.jsx new file mode 100644 index 0000000000..6f843ae57a --- /dev/null +++ b/frontend/assets/images/icons/widgets/horizontalDivider.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const HorizontalDivider = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => ( + + + + + + + + + + + +); + +export default HorizontalDivider; diff --git a/frontend/assets/images/icons/widgets/index.jsx b/frontend/assets/images/icons/widgets/index.jsx index 24705fdc6f..82ec948164 100644 --- a/frontend/assets/images/icons/widgets/index.jsx +++ b/frontend/assets/images/icons/widgets/index.jsx @@ -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 ; case 'datetimepickerv2': return ; + case 'emailinput': + return ; + case 'phoneinput': + return ; case 'daterangepicker': return ; case 'horizontaldivider': - return ; - case 'divider-horizondal': - return ; + return ; case 'downstatistics': return ; case 'dropdown': @@ -180,6 +183,7 @@ const WidgetIcon = (props) => { case 'text': return ; case 'textarea': + case 'textarealegacy': return - ); -}; diff --git a/frontend/src/Editor/Components/Timer.jsx b/frontend/src/Editor/Components/Timer.jsx index b7b1f529fd..d40dfcac40 100644 --- a/frontend/src/Editor/Components/Timer.jsx +++ b/frontend/src/Editor/Components/Timer.jsx @@ -164,19 +164,25 @@ export const Timer = function Timer({ height, properties = {}, styles, setExpose
{state === 'initial' && ( onStart()} > Start )} {state === 'running' && ( - + Pause )} {state === 'paused' && ( - + Resume )} diff --git a/frontend/src/Editor/Components/VerticalDivider.jsx b/frontend/src/Editor/Components/VerticalDivider.jsx new file mode 100644 index 0000000000..76042c8908 --- /dev/null +++ b/frontend/src/Editor/Components/VerticalDivider.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +const DASH_WIDTH = 4; +const DASH_GAP = 4; + +export const VerticalDivider = function Divider({ styles, height, width, dataCy, darkMode, properties }) { + const { dividerColor, boxShadow, dividerStyle } = styles; + const color = + dividerColor === '' || ['#000', '#000000'].includes(dividerColor) ? (darkMode ? '#fff' : '#000') : dividerColor; + + return ( +
+
+
+ ); +}; diff --git a/frontend/src/Editor/Components/verticalDivider.jsx b/frontend/src/Editor/Components/verticalDivider.jsx deleted file mode 100644 index ba6a38748c..0000000000 --- a/frontend/src/Editor/Components/verticalDivider.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -export const VerticalDivider = function Divider({ styles, height, width, dataCy, darkMode }) { - const { visibility, dividerColor, boxShadow } = styles; - const color = - dividerColor === '' || ['#000', '#000000'].includes(dividerColor) ? (darkMode ? '#fff' : '#000') : dividerColor; - - return ( -
-
-
-
- ); -}; diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index a44f19465b..1e5efeee2d 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -49,7 +49,7 @@ import { v4 as uuid } from 'uuid'; import Skeleton from 'react-loading-skeleton'; import EditorHeader from './Header'; import { getWorkspaceId, isValidUUID, Constants } from '@/_helpers/utils'; -import { fetchAndSetWindowTitle, pageTitles, defaultWhiteLabellingSettings } from '@white-label/whiteLabelling'; +import { fetchAndSetWindowTitle, pageTitles, retrieveWhiteLabelText } from '@white-label/whiteLabelling'; import '@/_styles/editor/react-select-search.scss'; import { withRouter } from '@/_hoc/withRouter'; import { ReleasedVersionError } from './AppVersionsManager/ReleasedVersionError'; @@ -277,7 +277,7 @@ const EditorComponent = (props) => { // 6. Unsubscribe from the observable when the component is unmounted return () => { - document.title = defaultWhiteLabellingSettings.WHITE_LABEL_TEXT; + document.title = retrieveWhiteLabelText(); socket && socket?.close(); subscription.unsubscribe(); if (window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true') props?.provider?.disconnect(); diff --git a/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx b/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx index aba2ca6af1..a30585bf42 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarDebugger/Logs.jsx @@ -27,7 +27,7 @@ function Logs({ logProps, idx }) { logProps?.description || (isString(logProps?.message) && logProps?.message) || (isString(logProps?.error?.description) && logProps?.error?.description) || //added string check since description can be an object. eg: runpy - logProps?.error?.message.trim() + logProps?.error?.message }`; const defaultStyles = { @@ -87,12 +87,12 @@ function Logs({ logProps, idx }) {

{ - setOpen((prev) => !prev); + logProps?.type !== 'Custom Log' && setOpen((prev) => !prev); }} style={{ pointerEvents: logProps?.isQuerySuccessLog ? 'none' : 'default', position: 'relative' }} > - + {logProps?.type !== 'Custom Log' && } {logProps.type === 'navToDisablePage' ? ( @@ -103,23 +103,32 @@ function Logs({ logProps, idx }) {

{logProps?.errorTarget}
{moment(logProps?.timestamp).fromNow()}
-
+ {logProps?.type === 'Custom Log' && ( +
+ Custom Log +
+ )} +
+ {logProps?.type == 'Custom Log' &&
{message}
} - {message} - {logProps?.error?.lineNumber ? `, Line ${logProps.error.lineNumber}` : ''} + {logProps?.type !== 'Custom Log' && message} )} diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx index 7a4a0b0bce..874de20b33 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx @@ -300,7 +300,7 @@ export const DateTimePicker = ({ return (
{ diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/styles.scss b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/styles.scss index 55d0e7f3ed..44eecf6ac5 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/styles.scss +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/styles.scss @@ -117,6 +117,9 @@ margin-top: 4px; box-shadow: 0px 8px 16px 0px #3032331A; } +.react-datepicker-popper { + z-index: 10001 !important; +} .react-datepicker-time__caption{ margin-left:20px @@ -234,4 +237,11 @@ line-height: normal !important; } } -} \ No newline at end of file +} +.table-schema-row{ + .react-datepicker-time__input-container{ + input{ + line-height: normal !important; + } + } +} diff --git a/frontend/src/Editor/WidgetManager/components.js b/frontend/src/Editor/WidgetManager/components.js index d7aa4442f0..6b0c4beb53 100644 --- a/frontend/src/Editor/WidgetManager/components.js +++ b/frontend/src/Editor/WidgetManager/components.js @@ -1,4 +1,4 @@ -import { widgets } from './widgetConfig'; +import { widgets } from '../../AppBuilder/WidgetManager/configs/widgetConfig'; const universalProps = { properties: {}, diff --git a/frontend/src/Editor/WidgetManager/configs/container.js b/frontend/src/Editor/WidgetManager/configs/container.js index 37a895f553..424b9a801d 100644 --- a/frontend/src/Editor/WidgetManager/configs/container.js +++ b/frontend/src/Editor/WidgetManager/configs/container.js @@ -44,7 +44,7 @@ export const containerConfig = { displayName: 'Show header', validation: { schema: { type: 'boolean' }, - defaultValue: true, + defaultValue: false, }, }, }, @@ -58,6 +58,7 @@ export const containerConfig = { }, displayName: 'ContainerText', properties: ['text'], + slotName: 'header', accessorKey: 'text', styles: ['fontWeight', 'textSize', 'textColor'], defaultValue: { @@ -71,7 +72,7 @@ export const containerConfig = { events: {}, styles: { backgroundColor: { - type: 'color', + type: 'colorSwatches', displayName: 'Background', validation: { schema: { type: 'string' }, @@ -80,7 +81,7 @@ export const containerConfig = { accordian: 'container', }, headerBackgroundColor: { - type: 'color', + type: 'colorSwatches', displayName: 'Background', validation: { schema: { type: 'string' }, @@ -89,7 +90,7 @@ export const containerConfig = { accordian: 'header', }, borderColor: { - type: 'color', + type: 'colorSwatches', displayName: 'Border color', validation: { schema: { type: 'string' }, @@ -153,7 +154,7 @@ export const containerConfig = { showOnMobile: { value: '{{false}}' }, }, properties: { - showHeader: { value: `{{true}}` }, + showHeader: { value: `{{false}}` }, loadingState: { value: `{{false}}` }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, @@ -163,6 +164,7 @@ export const containerConfig = { backgroundColor: { value: '#fff' }, headerBackgroundColor: { value: '#fff' }, borderRadius: { value: '4' }, + headerHeight: { value: '{{80}}' }, borderColor: { value: '#fff' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, }, diff --git a/frontend/src/Editor/WidgetManager/configs/divider.js b/frontend/src/Editor/WidgetManager/configs/divider.js index 27ce6edf8e..82bb38da6f 100644 --- a/frontend/src/Editor/WidgetManager/configs/divider.js +++ b/frontend/src/Editor/WidgetManager/configs/divider.js @@ -1,5 +1,5 @@ export const dividerConfig = { - name: 'horizontalDivider', + name: 'HorizontalDivider', displayName: 'Horizontal Divider', description: 'Separator between components', component: 'Divider', @@ -11,15 +11,12 @@ export const dividerConfig = { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, - properties: {}, - events: {}, - styles: { - dividerColor: { - type: 'color', - displayName: 'Divider color', + properties: { + label: { + type: 'code', + displayName: 'Label', validation: { schema: { type: 'string' }, - defaultValue: '#000000', }, }, visibility: { @@ -29,6 +26,83 @@ export const dividerConfig = { schema: { type: 'boolean' }, defaultValue: true, }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + events: {}, + styles: { + dividerColor: { + type: 'color', + displayName: 'Divider color', + validation: { + schema: { type: 'string' }, + defaultValue: '#000000', + }, + accordian: 'Divider', + }, + dividerStyle: { + type: 'switch', + displayName: 'Style', + validation: { + schema: { type: 'string' }, + }, + options: [ + { displayName: 'Solid', value: 'solid' }, + { displayName: 'Dashed', value: 'dashed' }, + ], + accordian: 'Divider', + }, + labelAlignment: { + type: 'switch', + displayName: 'Label alignment', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: true, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignhorizontalcenter', value: 'center', iconName: 'alignhorizontalcenter' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'Divider', + isFxNotRequired: true, + }, + labelColor: { + type: 'color', + displayName: 'Label Color', + validation: { + schema: { type: 'string' }, + }, + accordian: 'Divider', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', + }, + accordian: 'Divider', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, }, exposedVariables: { @@ -39,11 +113,19 @@ export const dividerConfig = { showOnDesktop: { value: '{{true}}' }, showOnMobile: { value: '{{false}}' }, }, - properties: {}, + properties: { + label: { value: '' }, + visibility: { value: '{{true}}' }, + tooltip: { value: '' }, + }, events: [], styles: { - visibility: { value: '{{true}}' }, - dividerColor: { value: '#000000' }, + dividerColor: { value: '#CCD1D5' }, + labelAlignment: { value: 'center' }, + dividerStyle: { value: 'solid' }, + labelColor: { value: '#6A727C' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/dropdownV2.js b/frontend/src/Editor/WidgetManager/configs/dropdownV2.js index de90dbd0bf..308aff1f36 100644 --- a/frontend/src/Editor/WidgetManager/configs/dropdownV2.js +++ b/frontend/src/Editor/WidgetManager/configs/dropdownV2.js @@ -63,6 +63,18 @@ export const dropdownV2Config = { }, accordian: 'Options', }, + sort: { + type: 'switch', + displayName: 'Sort options', + validation: { schema: { type: 'string' }, defaultValue: 'asc' }, + options: [ + { displayName: 'None', value: 'none' }, + { displayName: 'a-z', value: 'asc' }, + { displayName: 'z-a', value: 'desc' }, + ], + accordian: 'Options', + isFxNotRequired: true, + }, loadingState: { type: 'toggle', displayName: 'Loading state', @@ -300,6 +312,7 @@ export const dropdownV2Config = { }, label: { value: 'Select' }, optionsLoadingState: { value: '{{false}}' }, + sort: { value: 'asc' }, placeholder: { value: 'Select an option' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, diff --git a/frontend/src/Editor/WidgetManager/configs/image.js b/frontend/src/Editor/WidgetManager/configs/image.js index 453d9b4ed6..b962c270a5 100644 --- a/frontend/src/Editor/WidgetManager/configs/image.js +++ b/frontend/src/Editor/WidgetManager/configs/image.js @@ -143,6 +143,15 @@ export const imageConfig = { }, accordian: 'Image', }, + alignment: { + type: 'alignButtons', + displayName: 'Alignment', + validation: { + schema: { type: 'string' }, + defaultValue: 'center', + }, + accordian: 'Image', + }, backgroundColor: { type: 'color', displayName: 'Background', @@ -255,6 +264,7 @@ export const imageConfig = { boxShadow: { value: '0px 0px 0px 0px #00000090' }, padding: { value: 'default' }, customPadding: { value: '{{0}}' }, + alignment: { value: 'center' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/multiselectV2.js b/frontend/src/Editor/WidgetManager/configs/multiselectV2.js index 6aefd71067..b603db9c4a 100644 --- a/frontend/src/Editor/WidgetManager/configs/multiselectV2.js +++ b/frontend/src/Editor/WidgetManager/configs/multiselectV2.js @@ -130,6 +130,18 @@ export const multiselectV2Config = { }, accordian: 'Options', }, + sort: { + type: 'switch', + displayName: 'Sort options', + validation: { schema: { type: 'string' }, defaultValue: 'asc' }, + options: [ + { displayName: 'None', value: 'none' }, + { displayName: 'a-z', value: 'asc' }, + { displayName: 'z-a', value: 'desc' }, + ], + accordian: 'Options', + isFxNotRequired: true, + }, loadingState: { type: 'toggle', displayName: 'Loading state', @@ -313,6 +325,7 @@ export const multiselectV2Config = { advanced: { value: `{{false}}` }, showAllOption: { value: '{{false}}' }, optionsLoadingState: { value: '{{false}}' }, + sort: { value: 'asc' }, placeholder: { value: 'Select the options' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, diff --git a/frontend/src/Editor/WidgetManager/configs/textarea.js b/frontend/src/Editor/WidgetManager/configs/textarea.js index c5e71be77d..d0e0364334 100644 --- a/frontend/src/Editor/WidgetManager/configs/textarea.js +++ b/frontend/src/Editor/WidgetManager/configs/textarea.js @@ -4,7 +4,7 @@ export const textareaConfig = { description: 'Multi-line text input', component: 'TextArea', defaultSize: { - width: 6, + width: 10, height: 100, }, others: { @@ -12,82 +12,291 @@ export const textareaConfig = { showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { - value: { + label: { type: 'code', - displayName: 'Default value', - validation: { - schema: { type: 'string' }, - defaultValue: 'default text', - }, + displayName: 'Label', + validation: { schema: { type: 'string' }, defaultValue: 'Label' }, }, placeholder: { type: 'code', displayName: 'Placeholder', validation: { schema: { type: 'string' }, - defaultValue: 'Placeholder text', + defaultValue: 'Enter your input', }, }, - }, - events: {}, - styles: { + value: { + type: 'code', + displayName: 'Default value', + validation: { + schema: { + type: 'string', + }, + defaultValue: 'Default value', + }, + }, + loadingState: { + type: 'toggle', + displayName: 'Loading state', + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, visibility: { type: 'toggle', displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', }, disabledState: { type: 'toggle', displayName: 'Disable', - validation: { - schema: { type: 'boolean' }, - defaultValue: false, + validation: { schema: { type: 'boolean' }, defaultValue: false }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, + validation: { + mandatory: { type: 'toggle', displayName: 'Make this field mandatory' }, + regex: { type: 'code', displayName: 'Regex', placeholder: '^[a-zA-Z0-9_ -]{3,16}$' }, + minLength: { type: 'code', displayName: 'Min length', placeholder: 'Enter min length' }, + maxLength: { type: 'code', displayName: 'Max length', placeholder: 'Enter max length' }, + customRule: { + type: 'code', + displayName: 'Custom validation', + placeholder: `{{components.text2.text=='yes'&&'valid'}}`, + }, + }, + events: { + onChange: { displayName: 'On change' }, + onEnterPressed: { displayName: 'On enter pressed' }, + onFocus: { displayName: 'On focus' }, + onBlur: { displayName: 'On blur' }, + }, + styles: { + color: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'label', + }, + alignment: { + type: 'switch', + displayName: 'Alignment', + validation: { schema: { type: 'string' }, defaultValue: 'side' }, + options: [ + { displayName: 'Side', value: 'side' }, + { displayName: 'Top', value: 'top' }, + ], + accordian: 'label', + }, + direction: { + type: 'switch', + displayName: '', + validation: { schema: { type: 'string' }, defaultValue: 'left' }, + showLabel: false, + isIcon: true, + options: [ + { displayName: 'alignleftinspector', value: 'left', iconName: 'alignleftinspector' }, + { displayName: 'alignrightinspector', value: 'right', iconName: 'alignrightinspector' }, + ], + accordian: 'label', + isFxNotRequired: true, + }, + width: { + type: 'slider', + displayName: 'Width', + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', }, + isFxNotRequired: true, + }, + auto: { + type: 'checkbox', + displayName: 'auto', + showLabel: false, + validation: { schema: { type: 'boolean' }, defaultValue: true }, + accordian: 'label', + conditionallyRender: { + key: 'alignment', + value: 'side', + }, + isFxNotRequired: true, + }, + + backgroundColor: { + type: 'color', + displayName: 'Background', + validation: { schema: { type: 'string' }, defaultValue: '#fff' }, + accordian: 'field', + }, + borderColor: { + type: 'color', + displayName: 'Border', + validation: { schema: { type: 'string' }, defaultValue: '#CCD1D5' }, + accordian: 'field', + }, + accentColor: { + type: 'color', + displayName: 'Accent', + validation: { schema: { type: 'string' }, defaultValue: '#4368E3' }, + accordian: 'field', + }, + textColor: { + type: 'color', + displayName: 'Text', + validation: { schema: { type: 'string' }, defaultValue: '#1B1F24' }, + accordian: 'field', + }, + errTextColor: { + type: 'color', + displayName: 'Error text', + validation: { schema: { type: 'string' }, defaultValue: '#D72D39' }, + accordian: 'field', + }, + icon: { + type: 'icon', + displayName: 'Icon', + validation: { schema: { type: 'string' }, defaultValue: 'IconHome2' }, + accordian: 'field', + visibility: false, + }, + iconColor: { + type: 'color', + displayName: 'Icon color', + validation: { schema: { type: 'string' }, defaultValue: '#CFD3D859' }, + accordian: 'field', + visibility: false, + showLabel: false, }, borderRadius: { - type: 'code', + type: 'numberInput', displayName: 'Border radius', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 6 }, + accordian: 'field', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', validation: { - schema: { type: 'number' }, - defaultValue: 4, + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', }, + accordian: 'field', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, }, exposedVariables: { - value: - 'ToolJet is an open-source low-code platform for building and deploying internal tools with minimal engineering efforts 🚀', + value: '', + isMandatory: false, + isVisible: true, + isDisabled: false, + isLoading: false, }, actions: [ { handle: 'setText', - displayName: 'Set Text', - params: [{ handle: 'text', displayName: 'text', defaultValue: 'New Text' }], + displayName: 'Set text', + params: [{ handle: 'text', displayName: 'text', defaultValue: 'New text' }], }, { handle: 'clear', displayName: 'Clear', }, + { + handle: 'setFocus', + displayName: 'Set focus', + }, + { + handle: 'setBlur', + displayName: 'Set blur', + }, + { + handle: 'disable', + displayName: 'Disable(deprecated)', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'visibility', + displayName: 'Visibility(deprecated)', + params: [{ handle: 'visibility', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisable', + displayName: 'Set disable', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setLoading', + displayName: 'Set loading', + params: [{ handle: 'loading', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, ], definition: { + validation: { + mandatory: { value: '{{false}}' }, + regex: { value: '' }, + minLength: { value: '' }, + maxLength: { value: '' }, + customRule: { value: '' }, + }, + others: { showOnDesktop: { value: '{{true}}' }, showOnMobile: { value: '{{false}}' }, }, properties: { - value: { - value: - 'ToolJet is an open-source low-code platform for building and deploying internal tools with minimal engineering efforts 🚀', - }, - placeholder: { value: 'Placeholder text' }, + value: { value: '' }, + label: { value: 'Label' }, + placeholder: { value: 'Enter your input' }, + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + loadingState: { value: '{{false}}' }, + tooltip: { value: '' }, }, events: [], styles: { - visibility: { value: '{{true}}' }, - disabledState: { value: '{{false}}' }, - borderRadius: { value: '{{4}}' }, + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, + accentColor: { value: '#4368E3' }, + errTextColor: { value: '#D72D39' }, + borderRadius: { value: '{{6}}' }, + backgroundColor: { value: '#fff' }, + iconColor: { value: '#CFD3D859' }, + direction: { value: 'left' }, + width: { value: '{{33}}' }, + alignment: { value: 'side' }, + color: { value: '#1B1F24' }, + auto: { value: '{{true}}' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, + icon: { value: 'IconHome2' }, + iconVisibility: { value: false }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/verticalDivider.js b/frontend/src/Editor/WidgetManager/configs/verticalDivider.js index 443526c3b8..34d9029e3a 100644 --- a/frontend/src/Editor/WidgetManager/configs/verticalDivider.js +++ b/frontend/src/Editor/WidgetManager/configs/verticalDivider.js @@ -1,17 +1,34 @@ export const verticalDividerConfig = { name: 'VerticalDivider', - displayName: 'Vertical Divider', + displayName: 'Vertical divider', description: 'Vertical line separator', component: 'VerticalDivider', defaultSize: { - width: 2, + width: 1, height: 100, }, others: { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, - properties: {}, + properties: { + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + section: 'additionalActions', + }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' }, defaultValue: 'Tooltip text' }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, + }, events: {}, styles: { dividerColor: { @@ -21,14 +38,42 @@ export const verticalDividerConfig = { schema: { type: 'string' }, defaultValue: '#000000', }, + accordian: 'Divider', }, - visibility: { - type: 'toggle', - displayName: 'Visibility', + dividerStyle: { + type: 'switch', + displayName: 'Style', validation: { - schema: { type: 'boolean' }, - defaultValue: true, + schema: { type: 'string' }, }, + options: [ + { displayName: 'Solid', value: 'solid' }, + { displayName: 'Dashed', value: 'dashed' }, + ], + accordian: 'Divider', + }, + boxShadow: { + type: 'boxShadow', + displayName: 'Box Shadow', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: '0px 0px 0px 0px #00000040', + }, + accordian: 'Divider', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'container', }, }, exposedVariables: { @@ -39,11 +84,16 @@ export const verticalDividerConfig = { showOnDesktop: { value: '{{true}}' }, showOnMobile: { value: '{{false}}' }, }, - properties: {}, + properties: { + visibility: { value: '{{true}}' }, + tooltip: { value: '' }, + }, events: [], styles: { - visibility: { value: '{{true}}' }, - dividerColor: { value: '#000000' }, + dividerColor: { value: '#CCD1D5' }, + dividerStyle: { value: 'solid' }, + padding: { value: 'default' }, + boxShadow: { value: '0px 0px 0px 0px #00000040' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/constants.js b/frontend/src/Editor/WidgetManager/constants.js index 8c593455e1..3e7144a412 100644 --- a/frontend/src/Editor/WidgetManager/constants.js +++ b/frontend/src/Editor/WidgetManager/constants.js @@ -4,4 +4,5 @@ export const LEGACY_ITEMS = [ 'MultiselectLegacy', 'RadioButtonLegacy', 'ModalLegacy', + 'TextareaLegacy', ]; diff --git a/frontend/src/HomePage/AppCard.jsx b/frontend/src/HomePage/AppCard.jsx index f49330b49c..9613467c95 100644 --- a/frontend/src/HomePage/AppCard.jsx +++ b/frontend/src/HomePage/AppCard.jsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; import cx from 'classnames'; import { AppMenu } from './AppMenu'; import moment from 'moment'; @@ -10,7 +10,6 @@ import urlJoin from 'url-join'; import { useTranslation } from 'react-i18next'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import BulkIcon from '@/_ui/Icon/BulkIcons'; - import { getPrivateRoute, getSubpath } from '@/_helpers/routes'; import { validateName, decodeEntities } from '@/_helpers/utils'; const { defaultIcon } = configs; @@ -32,6 +31,10 @@ export default function AppCard({ const [isMenuOpen, setMenuOpen] = useState(false); const { t } = useTranslation(); const navigate = useNavigate(); + const cardRef = useRef(); + const [popoverVisible, setPopoverVisible] = useState(true); + const [isNameOverflowing, setIsNameOverflowing] = useState(false); + const tooltipRef = useRef(null); const onMenuToggle = useCallback( (status) => { @@ -53,10 +56,46 @@ export default function AppCard({ return validate.status; }; + useEffect(() => { + const checkOverflow = () => { + if (tooltipRef.current) { + setIsNameOverflowing(tooltipRef.current.scrollWidth > tooltipRef.current.clientWidth); + } + }; + + checkOverflow(); + window.addEventListener('resize', checkOverflow); + return () => window.removeEventListener('resize', checkOverflow); + }, []); + useEffect(() => { !isMenuOpen && setFocused(!!isHovered); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isHovered]); + }, [isHovered, isMenuOpen]); + + useEffect(() => { + const callBackFunction = (entries) => { + const [entry] = entries; + setPopoverVisible(isMenuOpen && entry.isIntersecting); + }; + + const options = { + root: null, + rootMargin: '0px', + threshold: 1.0, + }; + + const currentCardRef = cardRef.current; + const observer = new IntersectionObserver(callBackFunction, options); + if (currentCardRef) { + observer.observe(currentCardRef); + } + + return () => { + if (currentCardRef) { + observer.unobserve(currentCardRef); + } + }; + }, [isMenuOpen]); const updated_at = app?.editing_version?.updated_at || app?.updated_at; const updated = moment(updated_at).fromNow(true); @@ -136,8 +175,28 @@ export default function AppCard({
); + function AppNameDisplay({ tooltipRef }) { + const AppName = ( +

+ {decodeEntities(app.name)} +

+ ); + + return isNameOverflowing ? ( + + {AppName} + + ) : ( + AppName + ); + } + return ( -
+
@@ -158,7 +217,9 @@ export default function AppCard({ canUpdateApp={canUpdateApp(app)} deleteApp={() => deleteApp(app)} exportApp={() => exportApp(app)} - isMenuOpen={isMenuOpen} + isMenuOpen={setMenuOpen} + popoverVisible={popoverVisible} + setMenuOpen={setMenuOpen} darkMode={darkMode} currentFolder={currentFolder} appType={appType} @@ -169,14 +230,7 @@ export default function AppCard({
- -

- {decodeEntities(app.name)} -

-
+
{canUpdate && ( @@ -200,10 +254,10 @@ export default function AppCard({ diff --git a/frontend/src/HomePage/AppMenu.jsx b/frontend/src/HomePage/AppMenu.jsx index b350797256..493f5d66b4 100644 --- a/frontend/src/HomePage/AppMenu.jsx +++ b/frontend/src/HomePage/AppMenu.jsx @@ -13,6 +13,8 @@ export const AppMenu = function AppMenu({ openAppActionModal, darkMode, currentFolder, + popoverVisible, + setMenuOpen, appType, appCreationMode, }) { @@ -40,9 +42,12 @@ export const AppMenu = function AppMenu({ return ( setMenuOpen(false)} + show={popoverVisible} + container={document.getElementsByClassName('home-page-content')[0]} overlay={
diff --git a/frontend/src/HomePage/ExportAppModal.jsx b/frontend/src/HomePage/ExportAppModal.jsx index 463687421a..1ccb7734fc 100644 --- a/frontend/src/HomePage/ExportAppModal.jsx +++ b/frontend/src/HomePage/ExportAppModal.jsx @@ -70,7 +70,7 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam }); } - if (item.kind === 'tooljetdb' && item.options.table_id) extractedIdData.push(item.options.table_id); + if (item.kind === 'tooljetdb' && item.options.tableId) extractedIdData.push(item.options.tableId); }); const uniqueSet = new Set(extractedIdData); const selectedVersiontable = Array.from(uniqueSet).map((item) => ({ table_id: item })); diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index ab50cbb05d..8ee5ab3cc8 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -8,6 +8,7 @@ import { libraryAppService, gitSyncService, licenseService, + pluginsService, } from '@/_services'; import { ConfirmDialog, AppModal } from '@/_components'; import Select from '@/_ui/Select'; @@ -113,7 +114,7 @@ class HomePageComponent extends React.Component { showUserGroupMigrationModal: false, showGroupMigrationBanner: true, shouldAutoImportPlugin: false, - dependentPluginsForTemplate: [], + dependentPlugins: [], dependentPluginsDetail: {}, }; } @@ -310,7 +311,7 @@ class HomePageComponent extends React.Component { const fileReader = new FileReader(); const fileName = file.name.replace('.json', '').substring(0, 50); fileReader.readAsText(file, 'UTF-8'); - fileReader.onload = (event) => { + fileReader.onload = async (event) => { const result = event.target.result; let fileContent; try { @@ -319,8 +320,26 @@ class HomePageComponent extends React.Component { toast.error(`Could not import: ${parseError}`); return; } - this.setState({ fileContent, fileName, showImportAppModal: true }); + + const importedAppDef = fileContent.app || fileContent.appV2; + const dataSourcesUsedInApps = []; + importedAppDef.forEach((appDefinition) => { + appDefinition?.definition?.appV2?.dataSources.forEach((dataSource) => { + dataSourcesUsedInApps.push(dataSource); + }); + }); + + const dependentPluginsResponse = await pluginsService.findDependentPlugins(dataSourcesUsedInApps); + const { pluginsToBeInstalled = [], pluginsListIdToDetailsMap = {} } = dependentPluginsResponse.data; + this.setState({ + fileContent, + fileName, + showImportAppModal: true, + dependentPlugins: pluginsToBeInstalled, + dependentPluginsDetail: { ...pluginsListIdToDetailsMap }, + }); }; + fileReader.onerror = (error) => { toast.error(`Could not import the app: ${error}`); return; @@ -348,12 +367,19 @@ class HomePageComponent extends React.Component { importJSON.app[0].appName = appName; } const requestBody = { organization_id, ...importJSON }; + let installedPluginsInfo = []; try { + if (this.state.dependentPlugins.length) { + ({ installedPluginsInfo = [] } = await pluginsService.installDependentPlugins( + this.state.dependentPlugins, + true + )); + } + const data = await appsService.importResource(requestBody); toast.success('App imported successfully.'); - this.setState({ - isImportingApp: false, - }); + this.setState({ isImportingApp: false }); + if (!isEmpty(data.imports.app)) { this.props.navigate(`/${getWorkspaceId()}/apps/${data.imports.app[0].id}`, { state: { commitEnabled: this.state.commitEnabled }, @@ -362,12 +388,13 @@ class HomePageComponent extends React.Component { this.props.navigate(`/${getWorkspaceId()}/database`); } } catch (error) { - this.setState({ - isImportingApp: false, - }); - if (error.statusCode === 409) { - return false; + if (installedPluginsInfo.length) { + const pluginsId = installedPluginsInfo.map((pluginInfo) => pluginInfo.id); + await pluginsService.uninstallPlugins(pluginsId); } + + this.setState({ isImportingApp: false }); + if (error.statusCode === 409) return false; toast.error(error?.error || error?.message || 'App import failed'); } }; @@ -380,7 +407,7 @@ class HomePageComponent extends React.Component { const data = await libraryAppService.deploy( id, appName, - this.state.dependentPluginsForTemplate, + this.state.dependentPlugins, this.state.shouldAutoImportPlugin ); this.setState({ deploying: false }); @@ -732,7 +759,7 @@ class HomePageComponent extends React.Component { selectedTemplate: template, ...(plugins_to_be_installed.length && { shouldAutoImportPlugin: true, - dependentPluginsForTemplate: plugins_to_be_installed, + dependentPlugins: plugins_to_be_installed, dependentPluginsDetail: { ...plugins_detail_by_id }, }), }); @@ -750,7 +777,7 @@ class HomePageComponent extends React.Component { this.setState({ showCreateAppFromTemplateModal: false, selectedTemplate: null, - dependentPluginsForTemplate: [], + dependentPlugins: [], dependentPluginsDetail: {}, shouldAutoImportPlugin: false, }); @@ -763,6 +790,20 @@ class HomePageComponent extends React.Component { closeCreateAppModal = () => { this.setState({ showCreateAppModal: false, showCreateModuleModal: false }); }; + + openImportAppModal = async () => { + this.setState({ showImportAppModal: true }); + }; + + closeImportAppModal = () => { + this.setState({ + showImportAppModal: false, + dependentPlugins: [], + dependentPluginsDetail: {}, + shouldAutoImportPlugin: false, + }); + }; + isWithinSevenDaysOfSignUp = (date) => { const currentDate = new Date().toISOString(); const differenceInTime = new Date(currentDate).getTime() - new Date(date).getTime(); @@ -836,7 +877,7 @@ class HomePageComponent extends React.Component { workflowInstanceLevelLimit, showUserGroupMigrationModal, showGroupMigrationBanner, - dependentPluginsForTemplate, + dependentPlugins, dependentPluginsDetail, } = this.state; const modalConfigs = { @@ -865,12 +906,14 @@ class HomePageComponent extends React.Component { modalType: 'import', closeModal: () => this.setState({ showImportAppModal: false }), processApp: this.importFile, - show: () => this.setState({ showImportAppModal: true }), + show: this.openImportAppModal, title: 'Import app', actionButton: 'Import app', actionLoadingButton: 'Importing', fileContent: fileContent, selectedAppName: fileName, + dependentPluginsDetail: dependentPluginsDetail, + dependentPlugins: dependentPlugins, }, template: { modalType: 'template', @@ -882,7 +925,7 @@ class HomePageComponent extends React.Component { actionLoadingButton: 'Creating', templateDetails: this.state.selectedTemplate, dependentPluginsDetail: dependentPluginsDetail, - dependentPluginsForTemplate: dependentPluginsForTemplate, + dependentPlugins: dependentPlugins, }, }; return ( diff --git a/frontend/src/LicenseTooltip/index.jsx b/frontend/src/LicenseTooltip/index.jsx index 7accc41376..efe4b1f28a 100644 --- a/frontend/src/LicenseTooltip/index.jsx +++ b/frontend/src/LicenseTooltip/index.jsx @@ -25,6 +25,7 @@ const LicenseTooltip = ({ 'Multi-environments': 'multiEnvironment', 'Import from git': 'gitSync', GitSync: 'gitSync', + 'Custom themes': 'customThemes', }; const generateMessage = () => { diff --git a/frontend/src/Routes/AppsRoute.jsx b/frontend/src/Routes/AppsRoute.jsx index 7ed530162d..b84a2b04ff 100644 --- a/frontend/src/Routes/AppsRoute.jsx +++ b/frontend/src/Routes/AppsRoute.jsx @@ -8,6 +8,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { handleAppAccess } from '@/_helpers/handleAppAccess'; import { getQueryParams } from '@/_helpers/routes'; import queryString from 'query-string'; +import useStore from '@/AppBuilder/_stores/store'; export const AppsRoute = ({ children, componentType }) => { const params = useParams(); @@ -20,6 +21,7 @@ export const AppsRoute = ({ children, componentType }) => { }); const clonedElement = React.cloneElement(children, extraProps); const navigate = useNavigate(); + const switchPage = useStore((state) => state.switchPage); /* any extra logic specifc to the route can be done @@ -29,6 +31,10 @@ export const AppsRoute = ({ children, componentType }) => { if (isValidSession) { onValidSession(); } + + // handle back and forward navigation + window.addEventListener('popstate', handleBrowserNavigation); + return () => window.removeEventListener('popstate', handleBrowserNavigation); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isValidSession]); @@ -69,5 +75,10 @@ export const AppsRoute = ({ children, componentType }) => { } }; + const handleBrowserNavigation = (e) => { + const { id, handle } = e.state; + switchPage(id, handle, [], true); + }; + return {clonedElement}; }; diff --git a/frontend/src/Routes/AuthRoute.jsx b/frontend/src/Routes/AuthRoute.jsx index 6feac586fe..0aa67028f1 100644 --- a/frontend/src/Routes/AuthRoute.jsx +++ b/frontend/src/Routes/AuthRoute.jsx @@ -69,14 +69,13 @@ export const AuthRoute = ({ children }) => { }; const verifyWhiteLabeling = (pathname) => { - const signupRegex = /^\/signup\/[^/]+$/; - const loginRegex = /^\/login\/[^/]+$/; - if (!signupRegex.test(pathname) && !loginRegex.test(pathname)) { - resetToDefaultWhiteLabels(); - } - const whiteLabelText = retrieveWhiteLabelText(); - const whiteLabelFavicon = retrieveWhiteLabelFavicon(); - setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location); + // TODO: assume this code only needs for cloud. + // const signupRegex = /^\/signup\/[^/]+$/; + // const loginRegex = /^\/login\/[^/]+$/; + // if (!signupRegex.test(pathname) && !loginRegex.test(pathname)) { + // resetToDefaultWhiteLabels(); + // } + setFaviconAndTitle(location); }; const fetchOrganizationDetails = () => { diff --git a/frontend/src/SignupPage/EmailComponent.jsx b/frontend/src/SignupPage/EmailComponent.jsx deleted file mode 100644 index ae67d8f996..0000000000 --- a/frontend/src/SignupPage/EmailComponent.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; - -export const EmailComponent = ({ prefilledEmail, email, handleChange, emailError, t }) => { - if (prefilledEmail) { - return ( -
- -

- {prefilledEmail} -

-
- ); - } else { - return ( -
- - - {emailError && {emailError}} -
- ); - } -}; diff --git a/frontend/src/SignupPage/SignupPage.jsx b/frontend/src/SignupPage/SignupPage.jsx deleted file mode 100644 index 1ad7c1bbbc..0000000000 --- a/frontend/src/SignupPage/SignupPage.jsx +++ /dev/null @@ -1,378 +0,0 @@ -import React from 'react'; -import { authenticationService } from '@/_services'; -import { toast } from 'react-hot-toast'; -import { Link, Navigate } from 'react-router-dom'; -import { validateEmail } from '../_helpers/utils'; -import { SignupInfoScreen } from '@/SuccessInfoScreen'; -import OnboardingNavbar from '@/_components/OnboardingNavbar'; -import { ButtonSolid } from '@/_components/AppButton'; -import EnterIcon from '../../assets/images/onboardingassets/Icons/Enter'; -import EyeHide from '../../assets/images/onboardingassets/Icons/EyeHide'; -import EyeShow from '../../assets/images/onboardingassets/Icons/EyeShow'; -import { withTranslation } from 'react-i18next'; -import Spinner from '@/_ui/Spinner'; -import SignupStatusCard from '../OnBoardingForm/SignupStatusCard'; -import { withRouter } from '@/_hoc/withRouter'; -import { onInvitedUserSignUpSuccess } from '@/_helpers/platform/utils/auth.utils'; -import { isEmpty } from 'lodash'; -import { EmailComponent } from './EmailComponent'; -import SSOLoginModule from '@/LoginPage/SSOLoginModule'; -import { checkWhiteLabelsDefaultState } from '@white-label/whiteLabelling'; -class SignupPageComponent extends React.Component { - constructor(props) { - super(props); - /* Need these params to organization signup work */ - const routeState = this.props?.location?.state; - this.organizationToken = routeState?.organizationToken; - this.inviteeEmail = routeState?.inviteeEmail; - this.inviteOrganizationId = props.organizationId; - this.paramInviteOrganizationSlug = props.params.organizationId; - this.redirectTo = location?.search?.split('redirectTo=')[1]; - - this.state = { - isLoading: false, - showPassword: false, - emailError: '', - disableOnEdit: false, - email: this.inviteeEmail || '', - defaultState: false, - }; - } - - componentDidMount() { - const errorMessage = this.props?.location?.state?.errorMessage; - if (errorMessage) { - toast.error(errorMessage); - } - checkWhiteLabelsDefaultState(this.inviteOrganizationId).then((res) => { - this.setState({ defaultState: res }); - }); - } - - backtoSignup = (email, name) => { - this.setState({ signupSuccess: false, email: email, name: name, disableOnEdit: true, password: '' }); - }; - darkMode = localStorage.getItem('darkMode') === 'true'; - - handleChange = (event) => { - this.setState({ [event.target.name]: event.target.value, emailError: '', disableOnEdit: false }); - }; - handleOnCheck = () => { - this.setState((prev) => ({ showPassword: !prev.showPassword })); - }; - signup = (e) => { - e.preventDefault(); - const { email, name, password } = this.state; - if (!password || !password.trim()) { - toast.error("Password shouldn't be empty or contain white space(s)", { - position: 'top-center', - }); - return; - } - this.setState({ isLoading: true }); - - if (!validateEmail(email)) { - this.setState({ isLoading: false, emailError: 'Invalid email' }); - return; - } - - const organizationToken = this.organizationToken; - if (organizationToken) { - /* different API */ - authenticationService - .activateAccountWithToken(email, password, organizationToken) - .then((response) => onInvitedUserSignUpSuccess(response, this.props.navigate)) - .catch((errorObj) => { - let errorMessage; - const isThereAnyErrorsArray = errorObj?.error?.length && typeof errorObj?.error?.[0] === 'string'; - if (isThereAnyErrorsArray) { - errorMessage = errorObj?.error?.[0]; - } else if (typeof errorObj?.error?.error === 'string') { - errorMessage = errorObj?.error?.error; - } - errorMessage && toast.error(errorMessage); - const emailError = errorObj?.error?.inputError; - this.setState({ isLoading: false, emailError }); - }); - } else { - authenticationService - .signup(email, name, password, this.inviteOrganizationId, this.redirectTo) - .then((response) => { - const { organizationInviteUrl } = response; - if (organizationInviteUrl) onInvitedUserSignUpSuccess(response, this.props.navigate); - // eslint-disable-next-line no-unused-vars - const { from } = this.props.location.state || { - from: { pathname: '/' }, - }; - this.setState({ isLoading: false, signupSuccess: true }); - }) - .catch((e) => { - toast.error(e?.error || 'Something went wrong!', { - position: 'top-center', - }); - this.setState({ isLoading: false }); - }); - } - }; - - isFormSignUpEnabled = () => { - const { configs } = this.props; - return this.inviteOrganizationId ? configs?.form?.enabled : configs?.form?.enable_sign_up; - }; - - setSignupOrganizationDetails = () => { - authenticationService.setSignUpOrganizationDetails( - this.inviteOrganizationId, - this.paramInviteOrganizationSlug, - this.organizationToken - ); - }; - - render() { - const { configs } = this.props; - const { isLoading, signupSuccess, defaultState } = this.state; - const comingFromInviteFlow = !!this.organizationToken; - const isSignUpButtonDisabled = - isLoading || - !this.state.email || - !this.state.password || - (isEmpty(this.state.name) && !comingFromInviteFlow) || - this.state.password.length < 5; - const shouldShowSignInCTA = !this.organizationToken; - const isAnySSOEnabled = - !!configs?.git?.enabled || - !!configs?.google?.enabled || - !!configs?.openid?.enabled || - !!configs?.saml?.enabled || - !!configs?.ldap?.enabled; - - const shouldShowSignupDisabledCard = - !this.organizationToken && !configs?.enable_sign_up && !configs?.form?.enable_sign_up; - const passwordLabelText = this.organizationToken ? 'Create a password' : 'Password'; - - return ( -
-
- - -
-
- { - /* If the configs don't have any organization id. that means the workspace slug is invalid */ - this.paramInviteOrganizationSlug && !configs?.id ? ( - - ) : ( - !signupSuccess && ( - <> -
-

- {this.props.t('loginSignupPage.signUp', `Sign up`)} -

- {this.inviteOrganizationId && ( - {`Sign up to the workspace - ${configs?.name}`} - )} - {shouldShowSignInCTA ? ( -
- {this.props.t('loginSignupPage.alreadyHaveAnAccount', `Already have an account? `)}   - - {this.props.t('loginSignupPage.signIn', `Sign in`)} - -
- ) : ( -
- )} - {shouldShowSignupDisabledCard ? ( - - ) : ( - <> - {(configs?.enable_sign_up || !!this.organizationToken) && ( -
- this.setSignupOrganizationDetails()} - organizationSlug={this.paramInviteOrganizationSlug} - buttonText="Sign up with" - /> - {isAnySSOEnabled && this.isFormSignUpEnabled() && ( -
-
-

- OR -

-
-
- )} -
- )} - {this.isFormSignUpEnabled() && ( - <> -
- {!comingFromInviteFlow && ( - <> - - {' '} - - )} - - -
- -
- {this.state.showPassword ? ( - - ) : ( - - )} -
- - {this.props.t( - 'loginSignupPage.passwordCharacter', - 'Password must be at least 5 characters' - )} - -
-
-
- - {isLoading ? ( -
- -
- ) : ( - <> - - {this.props.t('loginSignupPage.getStartedForFree', 'Get started for free')} - - - - )} -
-
- - )} - - )} - - {defaultState && ( -

- By signing up you are agreeing to the -
- - - Terms of Service{' '} - - & - - {' '} - Privacy Policy - - -

- )} -
- - ) - ) - } - {signupSuccess && ( -
- -
- )} - -
-
-
- ); - } -} - -export const SignupPage = withTranslation()(withRouter(SignupPageComponent)); diff --git a/frontend/src/SignupPage/index.js b/frontend/src/SignupPage/index.js deleted file mode 100644 index 800338c079..0000000000 --- a/frontend/src/SignupPage/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './SignupPage'; diff --git a/frontend/src/SuccessInfoScreen/VerificationSuccessInfoScreen.jsx b/frontend/src/SuccessInfoScreen/VerificationSuccessInfoScreen.jsx index dcb8884af0..5b250efc3d 100644 --- a/frontend/src/SuccessInfoScreen/VerificationSuccessInfoScreen.jsx +++ b/frontend/src/SuccessInfoScreen/VerificationSuccessInfoScreen.jsx @@ -28,7 +28,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr const [showPassword, setShowPassword] = useState(false); const [fallBack, setFallBack] = useState(false); const { t } = useTranslation(); - const [defaultState, setDefaultState] = useState(false); + const defaultState = checkWhiteLabelsDefaultState(); const location = useLocation(); const params = useParams(); @@ -83,7 +83,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr (configs) => { setIsGettingConfigs(false); setConfigs(configs); - setFaviconAndTitle(null, null, location); + setFaviconAndTitle(location); }, () => { setIsGettingConfigs(false); @@ -92,9 +92,6 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr } else { setIsGettingConfigs(false); } - checkWhiteLabelsDefaultState(organizationId).then((res) => { - setDefaultState(res); - }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/frontend/src/_components/AppLogo.jsx b/frontend/src/_components/AppLogo.jsx index f461286071..13eba4aa78 100644 --- a/frontend/src/_components/AppLogo.jsx +++ b/frontend/src/_components/AppLogo.jsx @@ -1,22 +1,16 @@ -import React from 'react'; -import Logo from '@assets/images/rocket.svg'; +import React, { useEffect } from 'react'; +import Logo from '@assets/images/tj-logo.svg'; import { retrieveWhiteLabelLogo } from '@white-label/whiteLabelling'; - +import useStore from '@/AppBuilder/_stores/store'; export default function AppLogo({ isLoadingFromHeader, className }) { - const url = retrieveWhiteLabelLogo(); + const url = useStore((store) => store.whiteLabelLogo); return ( <> {url ? ( ) : ( - <> - {isLoadingFromHeader ? ( - - ) : ( - - )} - + <>{isLoadingFromHeader ? : } )} ); diff --git a/frontend/src/_components/AppModal.jsx b/frontend/src/_components/AppModal.jsx index 7d45e4c36e..54c623dbed 100644 --- a/frontend/src/_components/AppModal.jsx +++ b/frontend/src/_components/AppModal.jsx @@ -29,7 +29,7 @@ export function AppModal({ handleCommitEnableChange, appType, dependentPluginsDetail = [], - dependentPluginsForTemplate = [], + dependentPlugins = [], }) { if (!selectedAppName && templateDetails) { selectedAppName = templateDetails?.name || ''; @@ -238,10 +238,10 @@ export function AppModal({
)}
- {dependentPluginsForTemplate && dependentPluginsForTemplate.length >= 1 && ( + {dependentPlugins && dependentPlugins.length >= 1 && (
e.stopPropagation()}>
diff --git a/frontend/src/_components/DynamicFormV2.jsx b/frontend/src/_components/DynamicFormV2.jsx new file mode 100644 index 0000000000..1a6269976f --- /dev/null +++ b/frontend/src/_components/DynamicFormV2.jsx @@ -0,0 +1,510 @@ +import React from 'react'; +import cx from 'classnames'; +import DataSourceSchemaManager from '@/_helpers/dataSourceSchemaManager'; +import Textarea from '@/_ui/Textarea'; +import Input from '@/_ui/Input'; +import Select from '@/_ui/Select'; +import Headers from '@/_ui/HttpHeaders'; +import Toggle from '@/_ui/Toggle'; +import InputV3 from '@/_ui/Input-V3'; +import { filter, find, isEmpty } from 'lodash'; +import { ButtonSolid } from './AppButton'; +import { useGlobalDataSourcesStatus } from '@/_stores/dataSourcesStore'; +import { canDeleteDataSource, canUpdateDataSource } from '@/_helpers'; +import { OverlayTrigger, Tooltip } from 'react-bootstrap'; +import { orgEnvironmentVariableService, orgEnvironmentConstantService } from '../_services'; +import { Constants } from '@/_helpers/utils'; + +const DynamicFormV2 = ({ + schema, + options, + optionchanged, + optionsChanged, + selectedDataSource, + isEditMode, + layout = 'vertical', + onBlur, + setDefaultOptions, + currentAppEnvironmentId, + isGDS, + validationMessages, + setValidationMessages, + clearValidationMessages, +}) => { + const uiProperties = schema['tj:ui:properties'] || {}; + const dsm = React.useMemo(() => new DataSourceSchemaManager(schema), [schema]); + const encryptedProperties = React.useMemo(() => dsm.getEncryptedProperties(), [dsm]); + const [conditionallyRequiredProperties, setConditionallyRequiredProperties] = React.useState([]); + const [workspaceVariables, setWorkspaceVariables] = React.useState([]); + const [currentOrgEnvironmentConstants, setCurrentOrgEnvironmentConstants] = React.useState([]); + const [computedProps, setComputedProps] = React.useState({}); + const [hasUserInteracted, setHasUserInteracted] = React.useState(false); + const [interactedFields, setInteractedFields] = React.useState(new Set()); + + const isHorizontalLayout = layout === 'horizontal'; + const prevDataSourceIdRef = React.useRef(selectedDataSource?.id); + + const globalDataSourcesStatus = useGlobalDataSourcesStatus(); + const { isEditing: isDataSourceEditing } = globalDataSourcesStatus; + + React.useEffect(() => { + if (isGDS) { + orgEnvironmentConstantService.getConstantsFromEnvironment(currentAppEnvironmentId).then((data) => { + const constants = { + globals: {}, + secrets: {}, + }; + data.constants.forEach((constant) => { + if (constant.type === Constants.Secret) { + constants.secrets[constant.name] = constant.value; + } else { + constants.globals[constant.name] = constant.value; + } + }); + + setCurrentOrgEnvironmentConstants(constants); + }); + + orgEnvironmentVariableService.getVariables().then((data) => { + const client_variables = {}; + const server_variables = {}; + data.variables.map((variable) => { + if (variable.variable_type === 'server') { + server_variables[variable.variable_name] = 'HiddenEnvironmentVariable'; + } else { + client_variables[variable.variable_name] = variable.value; + } + }); + + setWorkspaceVariables({ client: client_variables, server: server_variables }); + }); + } + + return () => { + setWorkspaceVariables([]); + setCurrentOrgEnvironmentConstants([]); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentAppEnvironmentId]); + + React.useEffect(() => { + if (!hasUserInteracted) return; + const { valid, errors } = dsm.validateData(options); + + if (valid) { + clearValidationMessages(); + } else { + setValidationMessages(errors, schema); + const requiredFields = errors + .filter((error) => error.keyword === 'required') + .map((error) => error.params.missingProperty); + setConditionallyRequiredProperties(requiredFields); + } + }, [options]); + + React.useEffect(() => { + const prevDataSourceId = prevDataSourceIdRef.current; + prevDataSourceIdRef.current = selectedDataSource?.id; + const uiProperties = schema['tj:ui:properties']; + if (!isEmpty(uiProperties)) { + let fields = {}; + let encryptedFieldsProps = {}; + const flipComponentDropdown = find(uiProperties, ['widget', 'dropdown-component-flip']); + + if (flipComponentDropdown) { + const selector = options?.[flipComponentDropdown?.key]?.value; + const commonFieldsFromSslCertificate = uiProperties[selector]?.ssl_certificate?.commonFields; + fields = { + ...commonFieldsFromSslCertificate, + ...flipComponentDropdown?.commonFields, + ...uiProperties[selector], + }; + } else { + fields = { ...uiProperties }; + } + + const processFields = (fieldsObject) => { + Object.keys(fieldsObject).forEach((key) => { + const field = fieldsObject[key]; + const { widget, encrypted, key: propertyKey } = field; + + if (!canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource()) { + encryptedFieldsProps[propertyKey] = { + disabled: !!selectedDataSource?.id, + }; + } else if (!isDataSourceEditing) { + if (widget === 'password' || encrypted) { + encryptedFieldsProps[propertyKey] = { + disabled: true, + }; + } + } else { + if ((widget === 'password' || encrypted) && !(propertyKey in computedProps)) { + encryptedFieldsProps[propertyKey] = { + disabled: !!selectedDataSource?.id, + }; + } + } + + // To check for nested dropdown-component-flip + if (widget === 'dropdown-component-flip') { + const selectedOption = options?.[field.key]?.value; + + if (field.commonFields) { + processFields(field.commonFields); + } + + if (selectedOption && fieldsObject[selectedOption]) { + processFields(fieldsObject[selectedOption]); + } + } + }); + }; + + processFields(fields); + + if (uiProperties.renderForm) { + Object.keys(uiProperties.renderForm).forEach((sectionKey) => { + const section = uiProperties.renderForm[sectionKey]; + const { inputs } = section; + if (inputs) { + processFields(inputs); + } + }); + } + + if (prevDataSourceId !== selectedDataSource?.id) { + setComputedProps({ ...encryptedFieldsProps }); + } else { + setComputedProps({ ...computedProps, ...encryptedFieldsProps }); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedDataSource?.id, options, isDataSourceEditing]); + + const getElement = (type) => { + switch (type) { + case 'password': + case 'text': + return Input; + case 'password-v3': + case 'text-v3': + return InputV3; + case 'textarea': + return Textarea; + case 'toggle': + return Toggle; + case 'react-component-headers': + return Headers; + // TODO: Move dropdown component flip logic to be handled here + // case 'dropdown-component-flip': + // return Select; + default: + return
Type is invalid
; + } + }; + + const getElementProps = (uiProperties) => { + const { label, description, widget, required, width, key, help_text: helpText, list, buttonText } = uiProperties; + + const isRequired = required || conditionallyRequiredProperties.includes(key); + const isEncrypted = widget === 'password-v3' || encryptedProperties.includes(key); + const currentValue = options?.[key]?.value; + + const handleOptionChange = (key, value, flag) => { + if (!hasUserInteracted) { + setHasUserInteracted(true); + } + setInteractedFields((prev) => new Set(prev).add(key)); + optionchanged(key, value, flag); + }; + + switch (widget) { + case 'password': + case 'text': + case 'textarea': { + return { + key, + widget, + label, + placeholder: isEncrypted ? '**************' : description, + className: cx('form-control', { + 'dynamic-form-encrypted-field': isEncrypted, + }), + style: { marginBottom: '0px !important' }, + helpText: helpText, + value: currentValue || '', + onChange: (e) => optionchanged(key, e.target.value, true), + isGDS: true, + workspaceVariables: [], + workspaceConstants: [], + encrypted: isEncrypted, + onBlur, + }; + } + case 'password-v3': + case 'text-v3': { + return { + key, + widget, + label, + placeholder: isEncrypted ? '**************' : description, + className: cx('form-control', { + 'dynamic-form-encrypted-field': isEncrypted, + }), + style: { marginBottom: '0px !important' }, + helpText: helpText, + value: currentValue || '', + onChange: (e) => handleOptionChange(key, e.target.value, true), + isGDS: true, + workspaceVariables: [], + workspaceConstants: [], + encrypted: isEncrypted, + onBlur, + isRequired: isRequired, + isValidatedMessages: + !hasUserInteracted || !interactedFields.has(key) + ? { valid: null, message: '' } // skip validation for initial render and untouched elements + : validationMessages[key] + ? { valid: false, message: validationMessages[key] } + : isRequired && !isEncrypted + ? { valid: true, message: '' } + : { valid: null, message: '' }, // handle optional && encrypted fields + isDisabled: !canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource(), + }; + } + case 'react-component-headers': { + let isRenderedAsQueryEditor; + if (isGDS) { + isRenderedAsQueryEditor = false; + } else { + isRenderedAsQueryEditor = !isGDS; + } + return { + getter: key, + options: isRenderedAsQueryEditor + ? options?.[key] ?? schema?.defaults?.[key] + : options?.[key]?.value ?? schema?.defaults?.[key]?.value, + optionchanged, + isRenderedAsQueryEditor, + workspaceConstants: currentOrgEnvironmentConstants, + isDisabled: !canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource(), + encrypted: isEncrypted, + buttonText, + width: width, + }; + } + case 'toggle': + return { + defaultChecked: currentValue, + checked: currentValue, + onChange: (e) => optionchanged(key, e.target.checked), + }; + case 'dropdown': + case 'dropdown-component-flip': + return { + options: list, + value: options?.[key]?.value || options?.[key], + onChange: (value) => optionchanged(key, value), + width: width || '100%', + encrypted: options?.[key]?.encrypted, + }; + default: + return {}; + } + }; + + const getLayout = (uiProperties) => { + if (isEmpty(uiProperties)) return null; + const flipComponentDropdown = isFlipComponentDropdown(uiProperties); + + if (flipComponentDropdown) { + return flipComponentDropdown; + } + + const handleEncryptedFieldsToggle = (event, field) => { + if (!canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource()) { + return; + } + const isEditing = computedProps[field]['disabled']; + if (isEditing) { + optionchanged(field, ''); + } else { + //Send old field value if editing mode disabled for encrypted fields + const newOptions = { ...options }; + const oldFieldValue = selectedDataSource?.['options']?.[field]; + if (oldFieldValue) { + optionsChanged({ ...newOptions, [field]: oldFieldValue }); + } else { + delete newOptions[field]; + optionsChanged({ ...newOptions }); + } + } + setComputedProps({ + ...computedProps, + [field]: { + ...computedProps[field], + disabled: !isEditing, + }, + }); + }; + + const renderLabel = (label, tooltip) => { + const labelElement = ( + + ); + + if (tooltip) { + return ( + {tooltip}} + > + {labelElement} + + ); + } + + return labelElement; + }; + + return ( +
+ {Object.keys(uiProperties).map((key) => { + const { label, widget, encrypted, className, key: propertyKey } = uiProperties[key]; + const Element = getElement(widget); + const isSpecificComponent = ['tooljetdb-operations', 'react-component-api-endpoint'].includes(widget); + + return ( +
+ {!isSpecificComponent && ( +
+ {label && + widget !== 'text-v3' && + widget !== 'password-v3' && + renderLabel(label, uiProperties[key].tooltip)} +
+ )} +
+ +
+
+ ); + })} +
+ ); + }; + + const FlipComponentDropdown = (uiProperties) => { + const flipComponentDropdowns = filter(uiProperties, ['widget', 'dropdown-component-flip']); + + const dropdownComponents = flipComponentDropdowns.map((flipComponentDropdown) => { + const selector = options?.[flipComponentDropdown?.key]?.value || options?.[flipComponentDropdown?.key]; + + return ( +
+
+ {flipComponentDropdown.commonFields && getLayout(flipComponentDropdown.commonFields)} + +
+ {(flipComponentDropdown.label || isHorizontalLayout) && ( + + )} + +
+ + + {isPasswordField && ( +
+ {isPasswordVisible ? ( + + ) : ( + + )} +
)} - ref={ref} - {...props} - /> + ); }); Input.displayName = 'Input'; diff --git a/frontend/src/components/ui/Input/InputUtils/InputUtils.jsx b/frontend/src/components/ui/Input/InputUtils/InputUtils.jsx index 5e09cec4fa..57128adf73 100644 --- a/frontend/src/components/ui/Input/InputUtils/InputUtils.jsx +++ b/frontend/src/components/ui/Input/InputUtils/InputUtils.jsx @@ -13,7 +13,7 @@ export const ValidationMessage = ({ response, validationMessage, className }) => htmlFor="validation" type="helper" size="default" - className={`tw-font-normal ${response === true ? 'tw-text-text-success' : 'tw-text-text-warning'}`} + className={`tw-font-normal ${response === true ? 'tw-text-text-success' : '!tw-text-text-warning'}`} data-cy="validation-label" > {validationMessage} diff --git a/frontend/src/modules/Appbuilder/components/ColorSwatches/ColorSwatches.jsx b/frontend/src/modules/Appbuilder/components/ColorSwatches/ColorSwatches.jsx new file mode 100644 index 0000000000..bf0839f842 --- /dev/null +++ b/frontend/src/modules/Appbuilder/components/ColorSwatches/ColorSwatches.jsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers'; +import BaseColorSwatches from '@/modules/common/components/BaseColorSwatches'; + +const ColorSwatches = (props) => { + return ; +}; + +export default withEditionSpecificComponent(ColorSwatches, 'Appbuilder'); diff --git a/frontend/src/modules/Appbuilder/components/ColorSwatches/index.js b/frontend/src/modules/Appbuilder/components/ColorSwatches/index.js new file mode 100644 index 0000000000..97c38817ea --- /dev/null +++ b/frontend/src/modules/Appbuilder/components/ColorSwatches/index.js @@ -0,0 +1 @@ +export { default } from './ColorSwatches'; diff --git a/frontend/src/modules/Appbuilder/components/ThemeSelect/ThemeSelect.jsx b/frontend/src/modules/Appbuilder/components/ThemeSelect/ThemeSelect.jsx new file mode 100644 index 0000000000..dcf433452e --- /dev/null +++ b/frontend/src/modules/Appbuilder/components/ThemeSelect/ThemeSelect.jsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers'; +const ThemeSelect = () => { + return <>; +}; +export default withEditionSpecificComponent(ThemeSelect, 'Appbuilder'); diff --git a/frontend/src/modules/Appbuilder/components/ThemeSelect/index.js b/frontend/src/modules/Appbuilder/components/ThemeSelect/index.js new file mode 100644 index 0000000000..59a41175bf --- /dev/null +++ b/frontend/src/modules/Appbuilder/components/ThemeSelect/index.js @@ -0,0 +1 @@ +export { default } from './ThemeSelect'; diff --git a/frontend/src/modules/Appbuilder/components/index.js b/frontend/src/modules/Appbuilder/components/index.js index 4adb32d020..b1778f177f 100644 --- a/frontend/src/modules/Appbuilder/components/index.js +++ b/frontend/src/modules/Appbuilder/components/index.js @@ -2,5 +2,7 @@ import CreateVersionModal from './CreateVersionModal'; import PromoteReleaseButton from './PromoteReleaseButton'; import LogoNavDropdown from './LogoNavDropdown'; import AppEnvironments from './AppEnvironments'; +import ThemeSelect from './ThemeSelect'; +import ColorSwatches from './ColorSwatches'; -export { CreateVersionModal, PromoteReleaseButton, LogoNavDropdown, AppEnvironments }; +export { CreateVersionModal, PromoteReleaseButton, LogoNavDropdown, AppEnvironments, ThemeSelect, ColorSwatches }; diff --git a/frontend/src/modules/WorkspaceSettings/components/BaseSSOConfigurationList/Configuration.scss b/frontend/src/modules/WorkspaceSettings/components/BaseSSOConfigurationList/Configuration.scss index 70d3b05ea2..666b828022 100644 --- a/frontend/src/modules/WorkspaceSettings/components/BaseSSOConfigurationList/Configuration.scss +++ b/frontend/src/modules/WorkspaceSettings/components/BaseSSOConfigurationList/Configuration.scss @@ -297,10 +297,18 @@ input:checked+.slider:before { } } + + + .theme-dark { .form-control { background-color: unset !important; } + + .react-tel-input .form-control { + background-color: inherit !important; // Or any default value you prefer + } + } .dark-theme { diff --git a/frontend/src/modules/WorkspaceSettings/components/ManageOrgConstantsSettings/ConstantForm.jsx b/frontend/src/modules/WorkspaceSettings/components/ManageOrgConstantsSettings/ConstantForm.jsx index 355ce29c31..193ff39e99 100644 --- a/frontend/src/modules/WorkspaceSettings/components/ManageOrgConstantsSettings/ConstantForm.jsx +++ b/frontend/src/modules/WorkspaceSettings/components/ManageOrgConstantsSettings/ConstantForm.jsx @@ -197,12 +197,12 @@ const ConstantForm = ({
-