diff --git a/.github/workflows/cypress-appbuilder.yml b/.github/workflows/cypress-appbuilder.yml index 67eb0ae432..8f0a20615e 100644 --- a/.github/workflows/cypress-appbuilder.yml +++ b/.github/workflows/cypress-appbuilder.yml @@ -13,16 +13,18 @@ jobs: Cypress-App-Builder: runs-on: ubuntu-22.04 if: | - contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') || - contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') || - contains(github.event.pull_request.labels.*.name, 'run-cypress') + contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ce') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ee') || + contains(github.event.pull_request.labels.*.name, 'run-cypress') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') strategy: matrix: edition: >- ${{ - contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') && fromJson('["ce"]') || - contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') && fromJson('["ee"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ce') && fromJson('["ce"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ee') && fromJson('["ee"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') || fromJson('[]') }} @@ -158,35 +160,35 @@ jobs: name: screenshots-appbuilder-${{ matrix.edition }} path: cypress-tests/cypress/screenshots - Cypress-App-builder-Subpath: - runs-on: ubuntu-22.04 - if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-subpath') + # Cypress-App-builder-Subpath: + # runs-on: ubuntu-22.04 + # if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || + # contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-subpath') - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.ref }} + # steps: + # - name: Checkout + # uses: actions/checkout@v3 + # with: + # ref: ${{ github.event.pull_request.head.ref }} - - name: Create Cypress environment file - id: create-json - uses: jsdaniell/create-json@1.1.2 - with: - name: "cypress.env.json" - json: ${{ secrets.CYPRESS_SECRETS }} - dir: "./cypress-tests" + # - name: Create Cypress environment file + # id: create-json + # uses: jsdaniell/create-json@1.1.2 + # with: + # name: "cypress.env.json" + # json: ${{ secrets.CYPRESS_SECRETS }} + # dir: "./cypress-tests" - - name: App Builder subpath - uses: cypress-io/github-action@v5 - with: - working-directory: ./cypress-tests - config: "baseUrl=http://localhost:80/apps/tooljet/" - config-file: cypress-app-builder.config.js + # - name: App Builder subpath + # uses: cypress-io/github-action@v5 + # with: + # working-directory: ./cypress-tests + # config: "baseUrl=http://localhost:80/apps/tooljet/" + # config-file: cypress-app-builder.config.js - - name: Capture Screenshots - uses: actions/upload-artifact@v4 - if: always() - with: - name: screenshots - path: cypress-tests/cypress/screenshots + # - name: Capture Screenshots + # uses: actions/upload-artifact@v4 + # if: always() + # with: + # name: screenshots + # path: cypress-tests/cypress/screenshots diff --git a/.github/workflows/cypress-marketplace.yml b/.github/workflows/cypress-marketplace.yml index 4d34523219..9ddf476e12 100644 --- a/.github/workflows/cypress-marketplace.yml +++ b/.github/workflows/cypress-marketplace.yml @@ -14,17 +14,19 @@ jobs: Cypress-Marketplace: runs-on: ubuntu-22.04 - if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || - contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') || - contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace') + if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ce') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ee') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') strategy: matrix: edition: >- ${{ contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') || - contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') && fromJson('["ce"]') || - contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace') && fromJson('["ee"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ce') && fromJson('["ce"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ee') && fromJson('["ee"]') || fromJson('[]') }} @@ -159,13 +161,12 @@ jobs: "password": "password" }' - - name: Create Cypress environment file id: create-json uses: jsdaniell/create-json@1.1.2 with: name: "cypress.env.json" - json: ${{ secrets.CYPRESS_SECRETS }} + json: ${{ secrets.CYPRESS_SECRETS_MARKETPLACE }} dir: "./cypress-tests" - name: Marketplace @@ -185,8 +186,8 @@ jobs: Cypress-Marketplace-Subpath: runs-on: ubuntu-22.04 - if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath') + if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath') steps: - name: Checkout diff --git a/.github/workflows/cypress-platform.yml b/.github/workflows/cypress-platform.yml index 0480db39f9..c6a0db4be6 100644 --- a/.github/workflows/cypress-platform.yml +++ b/.github/workflows/cypress-platform.yml @@ -13,15 +13,18 @@ jobs: Cypress-Platform: runs-on: ubuntu-22.04 if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || - contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform') || - contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform') + contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') + strategy: matrix: edition: >- ${{ contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') || - contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform') && fromJson('["ce"]') || - contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform') && fromJson('["ee"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') && fromJson('["ce"]') || + contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') && fromJson('["ee"]') || fromJson('[]') }} diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index d1ca481d02..c5cb025cba 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -80,7 +80,7 @@ jobs: }, { "key": "PG_USER", - "value": "tooljet" + "value": "postgres" }, { "key": "PG_PASS", @@ -100,7 +100,7 @@ jobs: }, { "key": "TOOLJET_DB_USER", - "value": "tooljet" + "value": "postgres" }, { "key": "TOOLJET_DB_PASS", @@ -116,7 +116,7 @@ jobs: }, { "key": "PGRST_DB_URI", - "value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ce-tjdb" + "value": "postgres://postgres:postgres@localhost/${{ env.PR_NUMBER }}-ce-tjdb" }, { "key": "PGRST_HOST", @@ -168,7 +168,11 @@ jobs: } ], "serviceDetails": { - "disk": null, + "disk": { + "name": "tooljet-ce-pr-${{ env.PR_NUMBER }}-postgresql", + "mountPath": "/var/lib/postgresql/13/main", + "sizeGB": 10 + }, "env": "docker", "envSpecificDetails": { "dockerCommand": "", @@ -279,35 +283,35 @@ jobs: console.log(e) } - - name: Install PostgreSQL client - run: | - sudo apt update - sudo apt install postgresql-client -y + # - name: Install PostgreSQL client + # run: | + # sudo apt update + # sudo apt install postgresql-client -y - - name: Wait after installing PostgreSQL - run: sleep 25 + # - name: Wait after installing PostgreSQL + # run: sleep 25 - - name: Drop PostgreSQL PR databases - env: - PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} - PGPORT: 5432 - PGUSER: ${{ secrets.RENDER_DS_PG_USER }} - PGDATABASE: ${{ env.PR_NUMBER }}-ce - PGTJBDATABASE: ${{ env.PR_NUMBER }}-ce-tjdb - run: | - if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then - echo "Database $PGDATABASE exists, deleting..." - PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" - else - echo "Database $PGDATABASE does not exist." - fi + # - name: Drop PostgreSQL PR databases + # env: + # PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} + # PGPORT: 5432 + # PGUSER: ${{ secrets.RENDER_DS_PG_USER }} + # PGDATABASE: ${{ env.PR_NUMBER }}-ce + # PGTJBDATABASE: ${{ env.PR_NUMBER }}-ce-tjdb + # run: | + # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then + # echo "Database $PGDATABASE exists, deleting..." + # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" + # else + # echo "Database $PGDATABASE does not exist." + # fi - if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then - echo "Database $PGTJBDATABASE exists, deleting..." - PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" - else - echo "Database $PGTJBDATABASE does not exist." - fi + # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then + # echo "Database $PGTJBDATABASE exists, deleting..." + # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" + # else + # echo "Database $PGTJBDATABASE does not exist." + # fi suspend-ce-review-app: if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ce-review-app' }} @@ -317,7 +321,7 @@ jobs: - name: Suspend service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -349,7 +353,7 @@ jobs: - name: Resume service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -389,6 +393,39 @@ jobs: 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 Enterprise Edition id: create-ee-deployment run: | @@ -404,7 +441,7 @@ jobs: "name": "ToolJet EE PR #${{ env.PR_NUMBER }}", "notifyOnFail": "default", "ownerId": "tea-caeo4bj19n072h3dddc0", - "repo": "https://github.com/ToolJet/ToolJet", + "repo": "'"$REPO_URL"'", "slug": "tooljet-ee-pr-${{ env.PR_NUMBER }}", "suspended": "not_suspended", "suspenders": [], @@ -420,7 +457,7 @@ jobs: }, { "key": "PG_USER", - "value": "tooljet" + "value": "postgres" }, { "key": "PG_PASS", @@ -440,7 +477,7 @@ jobs: }, { "key": "TOOLJET_DB_USER", - "value": "tooljet" + "value": "postgres" }, { "key": "TOOLJET_DB_PASS", @@ -456,7 +493,7 @@ jobs: }, { "key": "PGRST_DB_URI", - "value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ee-tjdb" + "value": "postgres://postgres:postgres@localhost/${{ env.PR_NUMBER }}-ee-tjdb" }, { "key": "PGRST_HOST", @@ -536,7 +573,11 @@ jobs: } ], "serviceDetails": { - "disk": null, + "disk": { + "name": "tooljet-ee-pr-${{ env.PR_NUMBER }}-postgresql", + "mountPath": "/var/lib/postgresql/13/main", + "sizeGB": 10 + }, "env": "docker", "envSpecificDetails": { "dockerCommand": "", @@ -549,7 +590,7 @@ jobs: "port": 80, "protocol": "TCP" }], - "plan": "starter", + "plan": "standard", "pullRequestPreviewsEnabled": "no", "region": "oregon", "url": "https://tooljet-ee-pr-${{ env.PR_NUMBER }}.onrender.com" @@ -647,35 +688,35 @@ jobs: console.log(e) } - - name: Install PostgreSQL client - run: | - sudo apt update - sudo apt install postgresql-client -y + # - name: Install PostgreSQL client + # run: | + # sudo apt update + # sudo apt install postgresql-client -y - - name: Wait after installing PostgreSQL - run: sleep 25 + # - name: Wait after installing PostgreSQL + # run: sleep 25 - - name: Drop PostgreSQL PR databases - env: - PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} - PGPORT: 5432 - PGUSER: ${{ secrets.RENDER_DS_PG_USER }} - PGDATABASE: ${{ env.PR_NUMBER }}-ee - PGTJBDATABASE: ${{ env.PR_NUMBER }}-ee-tjdb - run: | - if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then - echo "Database $PGDATABASE exists, deleting..." - PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" - else - echo "Database $PGDATABASE does not exist." - fi + # - name: Drop PostgreSQL PR databases + # env: + # PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} + # PGPORT: 5432 + # PGUSER: ${{ secrets.RENDER_DS_PG_USER }} + # PGDATABASE: ${{ env.PR_NUMBER }}-ee + # PGTJBDATABASE: ${{ env.PR_NUMBER }}-ee-tjdb + # run: | + # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then + # echo "Database $PGDATABASE exists, deleting..." + # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" + # else + # echo "Database $PGDATABASE does not exist." + # fi - if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then - echo "Database $PGTJBDATABASE exists, deleting..." - PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" - else - echo "Database $PGTJBDATABASE does not exist." - fi + # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then + # echo "Database $PGTJBDATABASE exists, deleting..." + # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" + # else + # echo "Database $PGTJBDATABASE does not exist." + # fi suspend-ee-review-app: if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ee-review-app' }} @@ -685,7 +726,7 @@ jobs: - name: Suspend service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') @@ -717,7 +758,7 @@ jobs: - name: Resume service run: | export SERVICE_ID=$(curl --request GET \ - --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --url 'https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --header 'accept: application/json' \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ jq -r '.[0].service.id') diff --git a/.version b/.version index afad818663..171a6a93d6 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.11.0 +3.12.1 diff --git a/cypress-tests/cypress-platform.config.js b/cypress-tests/cypress-platform.config.js index a53733a70d..b565a0c1d1 100644 --- a/cypress-tests/cypress-platform.config.js +++ b/cypress-tests/cypress-platform.config.js @@ -39,11 +39,11 @@ module.exports = defineConfig({ chromeWebSecurity: false, trashAssetsBeforeRuns: true, e2e: { - setupNodeEvents(on, config) { + setupNodeEvents (on, config) { config.baseUrl = environment.baseUrl; on("task", { - readPdf(pathToPdf) { + readPdf (pathToPdf) { return new Promise((resolve) => { const pdfPath = path.resolve(pathToPdf); let dataBuffer = fs.readFileSync(pdfPath); @@ -55,7 +55,7 @@ module.exports = defineConfig({ }); on("task", { - readXlsx(filePath) { + readXlsx (filePath) { return new Promise((resolve, reject) => { try { let dataBuffer = fs.readFileSync(filePath); @@ -69,7 +69,7 @@ module.exports = defineConfig({ }); on("task", { - deleteFolder(folderName) { + deleteFolder (folderName) { return new Promise((resolve, reject) => { rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => { if (err) { @@ -83,7 +83,7 @@ module.exports = defineConfig({ }); on("task", { - dbConnection({ dbconfig, sql }) { + dbConnection ({ dbconfig, sql }) { const client = new pg.Pool(dbconfig); return client.query(sql); }, @@ -97,9 +97,9 @@ module.exports = defineConfig({ baseUrl: environment.baseUrl, configFile: environment.configFile, specPattern: [ - "cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js", - "cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js", - "cypress/e2e/happyPath/platform/ceTestcases/!(userFlow)/**/*.cy.js", + "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", + "cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js", + "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug).cy.js", "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", ], numTestsKeptInMemory: 1, diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index e9891c962e..c625e6cc33 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -689,7 +689,7 @@ Cypress.Commands.add( name: dataSourceName, options: [ { key: "connection_type", value: "manual", encrypted: false }, - { key: "host", value: "35.238.9.114" }, + { key: "host", value: "9.234.17.31" }, { key: "port", value: 5432 }, { key: "database", value: "student" }, { key: "username", value: "postgres" }, diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index 10c849d807..d242eb1895 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -6,6 +6,7 @@ import { passwordInputText } from "Texts/passwordInput"; import { importSelectors } from "Selectors/exportImport"; import { importText } from "Texts/exportImport"; import { onboardingSelectors } from "Selectors/onboarding"; +import { selectAppCardOption } from "Support/utils/common"; const API_ENDPOINT = Cypress.env("environment") === "Community" @@ -160,13 +161,15 @@ Cypress.Commands.add( Cypress.Commands.add("deleteApp", (appName) => { cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); - cy.get(commonSelectors.appCard(appName)) - .realHover() - .find(commonSelectors.appCardOptionsButton) - .realHover() - .click(); - cy.get(commonSelectors.deleteAppOption).click(); + selectAppCardOption( + appName, + commonSelectors.appCardOptions(commonText.deleteAppOption) + ); cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); + cy.verifyToastMessage( + commonSelectors.toastMessage, + commonText.appDeletedToast + ); cy.wait("@appDeleted"); }); @@ -394,39 +397,37 @@ Cypress.Commands.add("getPosition", (componentName) => { }); Cypress.Commands.add("defaultWorkspaceLogin", () => { - cy.apiLogin(); + cy.task("dbConnection", { + dbconfig: Cypress.env("app_db"), + sql: ` + SELECT id FROM organizations WHERE name = 'My workspace';`, + }).then((resp) => { + const workspaceId = resp.rows[0].id; - // cy.intercept("GET", API_ENDPOINT).as("library_apps"); - cy.visit("/my-workspace"); - cy.wait(2000) - cy.get(commonSelectors.homePageLogo, { timeout: 10000 }); - // cy.wait("@library_apps"); + cy.apiLogin( + "dev@tooljet.io", + "password", + workspaceId, + "/my-workspace" + ).then(() => { + cy.visit("/"); + cy.wait(2000); + cy.get(commonSelectors.homePageLogo, { timeout: 10000 }); + }); + }); }); -Cypress.Commands.add( - "visitSlug", - ({ - actualUrl, - errorUrls = [ - `${Cypress.config("baseUrl")}/error/unknown`, - `${Cypress.config("baseUrl")}/error/restricted`, - ], - }) => { - if (!actualUrl) { - throw new Error("actualUrl is required for visitSlug command."); +Cypress.Commands.add("visitSlug", ({ actualUrl }) => { + cy.visit(actualUrl); + cy.wait(1000); + + cy.url().then((currentUrl) => { + if (currentUrl !== actualUrl) { + cy.visit(actualUrl); + cy.wait(1000); } - - cy.visit(actualUrl); - - cy.url().then((url) => { - if (errorUrls.includes(url)) { - cy.log(`Navigation resulted in error URL: ${url}. Retrying...`); - cy.visit(actualUrl); - cy.wait(1000); - } - }); - } -); + }); +}); Cypress.Commands.add("releaseApp", () => { @@ -513,13 +514,58 @@ Cypress.Commands.overwrite( } ); +Cypress.Commands.add("installMarketplacePlugin", (pluginName) => { + const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`; + + cy.visit(MARKETPLACE_URL); + cy.wait(1000); + + cy.get('[data-cy="-list-item"]').eq(0).click(); + cy.wait(1000); + + cy.get("body").then(($body) => { + if ($body.find(".plugins-card").length === 0) { + cy.log("No plugins found, proceeding to install..."); + installPlugin(pluginName); + } else { + cy.get(".plugins-card").then(($cards) => { + const isInstalled = $cards.toArray().some((card) => { + return ( + Cypress.$(card) + .find(".font-weight-medium.text-capitalize") + .text() + .trim() === pluginName + ); + }); + + if (isInstalled) { + cy.log(`${pluginName} is already installed. Skipping installation.`); + cy.get(commonSelectors.globalDataSourceIcon).click(); + } else { + installPlugin(pluginName); + cy.get(commonSelectors.globalDataSourceIcon).click(); + } + }); + } + }); + + function installPlugin (pluginName) { + cy.get('[data-cy="-list-item"]').eq(1).click(); + cy.wait(1000); + + cy.contains(".plugins-card", pluginName).within(() => { + cy.get(".marketplace-install").click(); + cy.wait(1000); + }); + } +}); + Cypress.Commands.add("verifyElement", (selector, text, eqValue) => { const element = eqValue !== undefined ? cy.get(selector).eq(eqValue) : cy.get(selector); element.should("be.visible").and("have.text", text); }); - Cypress.Commands.add("getAppId", (appName) => { cy.task("dbConnection", { dbconfig: Cypress.env("app_db"), @@ -529,3 +575,33 @@ Cypress.Commands.add("getAppId", (appName) => { return appId; }); }); + +Cypress.Commands.add("uninstallMarketplacePlugin", (pluginName) => { + const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`; + + cy.visit(MARKETPLACE_URL); + cy.wait(1000); + + cy.get('[data-cy="-list-item"]').eq(0).click(); + cy.wait(1000); + + cy.get(".plugins-card").each(($card) => { + cy.wrap($card) + .find(".font-weight-medium.text-capitalize") + .invoke("text") + .then((text) => { + if (text.trim() === pluginName) { + cy.wrap($card).find(".link-primary").contains("Remove").click(); + cy.wait(1000); + + cy.get('[data-cy="delete-plugin-title"]').should("be.visible"); + cy.get('[data-cy="yes-button"]').click(); + cy.wait(2000); + + cy.log(`${pluginName} has been successfully uninstalled.`); + } else { + cy.log(`${pluginName} is not installed. Skipping uninstallation.`); + } + }); + }); +}); diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index 42e3378825..ec104d67bc 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -6,7 +6,8 @@ export const commonSelectors = { toastMessage: ".go3958317564", oldToastMessage: ".go318386747", appSlugAccept: '[data-cy="app-slug-accepted-label"]', - newToastMessage: '.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551 > .go3958317564', + newToastMessage: + '.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551 > .go3958317564', toastCloseButton: '[data-cy="toast-close-button"]', editButton: "[data-cy=edit-button]", workspaceConstantNameInput: '[data-cy="name-input-field"]', @@ -18,7 +19,7 @@ export const commonSelectors = { appCardOptionsButton: "[data-cy=app-card-menu-icon]", autoSave: "[data-cy=autosave-indicator]", nameInputFieldd: "[data-cy=name-input-field]", - valueInputFieldd: '[data-cy=value-input-field]', + valueInputFieldd: "[data-cy=value-input-field]", skipButton: ".driver-close-btn", skipInstallationModal: "[data-cy=skip-button]", homePageLogo: "[data-cy=home-page-logo]", @@ -395,7 +396,7 @@ export const commonWidgetSelector = { modalCloseButton: '[data-cy="modal-close-button"]', iframeLinkLabel: '[data-cy="iframe-link-label"]', ifameLinkCopyButton: '[data-cy="iframe-link-copy-button"]', - appSlugLabel: '[data-cy="input-field-label"]', + appSlugLabel: '[data-cy="unique-app-slug-field-label"]', appSlugInput: '[data-cy="app-slug-input-field"]', appSlugInfoLabel: '[data-cy="helper-text"]', appLinkLabel: '[data-cy="app-link-label"]', diff --git a/cypress-tests/cypress/constants/selectors/restAPI.js b/cypress-tests/cypress/constants/selectors/restAPI.js index a24c115712..5ec2d73954 100644 --- a/cypress-tests/cypress/constants/selectors/restAPI.js +++ b/cypress-tests/cypress/constants/selectors/restAPI.js @@ -24,7 +24,7 @@ export const restAPISelector = { return `[data-cy="${cyParamName(header)}-delete-button-${cyParamName(index)}"]`; }, addMoreButton: (header) => { - return `[data-cy="${cyParamName(header)}-add-more-button"]`; + return `[data-cy="${cyParamName(header)}-add-button"]`; }, dropdownLabel: (label) => { return `[data-cy="${cyParamName(label)}-dropdown-label"]`; diff --git a/cypress-tests/cypress/constants/texts/postgreSql.js b/cypress-tests/cypress/constants/texts/postgreSql.js index f7c9baf984..9db745b58d 100644 --- a/cypress-tests/cypress/constants/texts/postgreSql.js +++ b/cypress-tests/cypress/constants/texts/postgreSql.js @@ -4,8 +4,8 @@ export const postgreSqlText = { allDataSources: () => { return Cypress.env("marketplace_action") - ? "All data sources (44)" - : "All data sources (45)"; + ? "All data sources (45)" + : "All data sources (43)"; }, commonlyUsed: "Commonly used (5)", allDatabase: () => { diff --git a/cypress-tests/cypress/constants/texts/version.js b/cypress-tests/cypress/constants/texts/version.js index 4f640db63a..ed92ea4c29 100644 --- a/cypress-tests/cypress/constants/texts/version.js +++ b/cypress-tests/cypress/constants/texts/version.js @@ -8,11 +8,7 @@ export const editVersionText = { export const deleteVersionText = { deleteModalText: (text) => { - // 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( + return `Are you sure you want to delete this version - ${cyParamName( text )}?`; }, diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/buttonHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/buttonHappyPath.cy.js index 67be49e530..acc65395f7 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/buttonHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/buttonHappyPath.cy.js @@ -32,7 +32,7 @@ import { addSupportCSAData, } from "Support/utils/events"; -describe("Editor- Test Button widget", () => { +describe("Editor- Test Button widget ", () => { beforeEach(() => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-button-App`); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js index 46f6339e11..51b65aeb60 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/addAllPluginsToApp.cy.js @@ -66,7 +66,7 @@ describe("Add all Data sources to app", () => { cy.apiLogin(); }); - it("Should verify global data source page", () => { + it.skip("Should verify global data source page", () => { cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); cy.visit(`${data.workspaceSlug}`); @@ -87,7 +87,7 @@ describe("Add all Data sources to app", () => { ); }); - it("Should add all data sources in data source page", () => { + it.skip("Should add all data sources in data source page", () => { cy.visit(`${data.workspaceSlug}`); dataSources.forEach((dsName) => { @@ -109,7 +109,7 @@ describe("Add all Data sources to app", () => { }); }); - it("Should add all data sources in the app", () => { + it.skip("Should add all data sources in the app", () => { cy.visit(`${data.workspaceSlug}`); cy.get(commonSelectors.dashboardIcon).click(); cy.get(commonSelectors.appCreateButton).click(); @@ -135,7 +135,7 @@ describe("Add all Data sources to app", () => { }); }); - it("Should install all makretplace plugins and add them into the app", () => { + it.skip("Should install all makretplace plugins and add them into the app", () => { cy.visit(`${data.workspaceSlug}`); const dataSourcesMarketplace = [ "Plivo", @@ -189,12 +189,15 @@ describe("Add all Data sources to app", () => { cy.wrap(dataSourcesMarketplace).each((dsName) => { cy.get(commonSelectors.globalDataSourceIcon).click(); selectAndAddDataSource("databases", dsName, dsName); - cy.wait(500); + cy.wait(1000); }); - cy.get(commonSelectors.dashboardIcon).click(); - cy.get(commonSelectors.appCreateButton).click(); - cy.get(commonSelectors.appNameInput).click().type(data.dsNamefake1); + cy.get(commonSelectors.dashboardIcon).should("be.visible").click(); + cy.get(commonSelectors.appCreateButton).should("be.visible").click(); + cy.get(commonSelectors.appNameInput) + .should("be.visible") + .click() + .type(data.dsNamefake1); cy.get(commonSelectors.createAppButton).click(); cy.skipWalkthrough(); @@ -203,7 +206,7 @@ describe("Add all Data sources to app", () => { cy.get(".css-4e90k9").type( `cypress-${cyParamName(dsName)}-${cyParamName(dsName)}` ); - cy.wait(500); + cy.wait(1000); cy.contains( `[id*="react-select-"]`, @@ -212,7 +215,7 @@ describe("Add all Data sources to app", () => { .should("be.visible") .click(); - cy.wait(500); + cy.wait(1000); }); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTable.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js similarity index 96% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTable.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js index 1ae1290180..0733373ece 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTable.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js @@ -19,13 +19,14 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); -data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + +data.queryName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source Airtable", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on connection AirTable form", () => { @@ -199,7 +200,7 @@ describe("Data source Airtable", () => { cy.get('[data-cy="show-ds-popover-button"]').click(); cy.get(".css-4e90k9").type(`${data.dsName}`); cy.contains(`[id*="react-select-"]`, data.dsName).click(); - cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName1); + cy.get('[data-cy="query-rename-input"]').clear().type(data.queryName); cy.get(airTableSelector.operationSelectDropdown) .click() @@ -225,7 +226,7 @@ describe("Data source Airtable", () => { cy.get(dataSourceSelector.queryPreviewButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - `Query (${data.dsName1}) completed.` + `Query (${data.queryName}) completed.` ); // Verify Delete record operation @@ -277,7 +278,7 @@ describe("Data source Airtable", () => { cy.get(dataSourceSelector.queryPreviewButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - `Query (${data.dsName1}) completed.` + `Query (${data.queryName}) completed.` ); deleteAppandDatasourceAfterExecution( data.dsName, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthena.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthenaHappyPath.cy.skip.js similarity index 92% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthena.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthenaHappyPath.cy.skip.js index 167ecdb2c5..f207f62058 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthena.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonAthenaHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source amazon athena", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on amazon athena connection form", () => { + it.skip("Should verify elements on amazon athena connection form", () => { const Accesskey = Cypress.env("amazonathena_accessKey"); const Secretkey = Cypress.env("amazonathena_secretKey"); const DbName = Cypress.env("amazonathena_DbName"); @@ -97,7 +97,7 @@ describe("Data source amazon athena", () => { deleteDatasource(`cypress-${data.dsName}-Amazon-Athena`); }); - it("Should verify the functionality of amazon athena connection form.", () => { + it.skip("Should verify the functionality of amazon athena connection form.", () => { const Accesskey = Cypress.env("amazonathena_accessKey"); const Secretkey = Cypress.env("amazonathena_secretKey"); const DbName = Cypress.env("amazonathena_DbName"); @@ -134,7 +134,7 @@ describe("Data source amazon athena", () => { deleteDatasource(`cypress-${data.dsName}-amazon-Athena`); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const Accesskey = Cypress.env("amazonathena_accessKey"); const Secretkey = Cypress.env("amazonathena_secretKey"); const DbName = Cypress.env("amazonathena_DbName"); @@ -188,11 +188,13 @@ describe("Data source amazon athena", () => { cy.get(".css-4e90k9").type(`${data.dsName}`); cy.contains(`[id*="react-select-"]`, data.dsName).click(); cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName); - + cy.get(`[data-cy="list-query-${data.dsName}"]`).should("be.visible"); cy.get('[data-cy="query-input-field"]').clearAndTypeOnCodeMirror( "SHOW DATABASES;" ); - + cy.get( + '[data-cy="query-input-field"] >>> .cm-editor >> .cm-content > .cm-line' + ).should("have.text", "SHOW DATABASES;"); cy.get(dataSourceSelector.queryPreviewButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonses.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonsesHappyPath.cy.skip.js similarity index 95% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonses.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonsesHappyPath.cy.skip.js index 674c694d28..9c3864c9fd 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonses.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/amazonsesHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source amazon ses", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on amazonses connection form", () => { + it.skip("Should verify elements on amazonses connection form", () => { const Accesskey = Cypress.env("amazonSes_accessKey"); const Secretkey = Cypress.env("amazonSes_secretKey"); @@ -80,7 +80,7 @@ describe("Data source amazon ses", () => { deleteDatasource(`cypress-${data.dsName}-Amazon-ses`); }); - it("Should verify the functionality of amazonses connection form.", () => { + it.skip("Should verify the functionality of amazonses connection form.", () => { selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName); cy.get(".react-select__dropdown-indicator").eq(1).click(); @@ -112,7 +112,7 @@ describe("Data source amazon ses", () => { deleteDatasource(`cypress-${data.dsName}-amazon-ses`); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const email = "adish" + "@" + "tooljet.com"; selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWrite.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWriteHappyPath.cy.skip.js similarity index 96% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWrite.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWriteHappyPath.cy.skip.js index 8ed50754ad..27f52fd99d 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWrite.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/appWriteHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source AppWrite", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on appwrite connection form", () => { + it.skip("Should verify elements on appwrite connection form", () => { const Host = Cypress.env("appwrite_host"); const ProjectID = Cypress.env("appwrite_projectID"); const DatabaseID = Cypress.env("appwrite_databaseID"); @@ -100,7 +100,7 @@ describe("Data source AppWrite", () => { deleteDatasource(`cypress-${data.dsName}-Appwrite`); }); - it("Should verify the functionality of appwrite connection form.", () => { + it.skip("Should verify the functionality of appwrite connection form.", () => { const Host = Cypress.env("appwrite_host"); const ProjectID = Cypress.env("appwrite_projectID"); const DatabaseID = Cypress.env("appwrite_databaseID"); @@ -150,7 +150,7 @@ describe("Data source AppWrite", () => { deleteDatasource(`cypress-${data.dsName}-Appwrite`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const Host = Cypress.env("appwrite_host"); const ProjectID = Cypress.env("appwrite_projectID"); const DatabaseID = Cypress.env("appwrite_databaseID"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambda.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambdaHappyPath.cy.skip.js similarity index 92% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambda.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambdaHappyPath.cy.skip.js index f127beb854..17a3f0426c 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambda.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsLambdaHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source AWS Lambda", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on AWS Lambda connection form", () => { + it.skip("Should verify elements on AWS Lambda connection form", () => { const Accesskey = Cypress.env("awslamda_access"); const Secretkey = Cypress.env("awslamda_secret"); @@ -80,9 +80,10 @@ describe("Data source AWS Lambda", () => { ); deleteDatasource(`cypress-${data.dsName}-aws-lambda`); + cy.uninstallMarketplacePlugin("AWS Lambda"); }); - it("Should verify the functionality of AWS Lambda connection form", () => { + it.skip("Should verify the functionality of AWS Lambda connection form", () => { const Accesskey = Cypress.env("awslamda_access"); const Secretkey = Cypress.env("awslamda_secret"); @@ -113,9 +114,10 @@ describe("Data source AWS Lambda", () => { ); deleteDatasource(`cypress-${data.dsName}-aws-lambda`); + cy.uninstallMarketplacePlugin("AWS Lambda"); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const Accesskey = Cypress.env("awslamda_access"); const Secretkey = Cypress.env("awslamda_secret"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextract.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextractHappyPath.cy.skip.js similarity index 94% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextract.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextractHappyPath.cy.skip.js index 7af21cf467..1a01ecc757 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextract.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/awsTextractHappyPath.cy.skip.js @@ -21,15 +21,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source AWS Textract", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on AWS Textract connection form", () => { + it.skip("Should verify elements on AWS Textract connection form", () => { const Accesskey = Cypress.env("awstextract_access"); const Secretkey = Cypress.env("awstextract_secret"); @@ -87,7 +87,7 @@ describe("Data source AWS Textract", () => { deleteDatasource(`cypress-${data.dsName}-aws-textract`); }); - it("Should verify functionality of AWS Textract connection form", () => { + it.skip("Should verify functionality of AWS Textract connection form", () => { const Accesskey = Cypress.env("awstextract_access"); const Secretkey = Cypress.env("awstextract_secret"); @@ -122,9 +122,10 @@ describe("Data source AWS Textract", () => { ); deleteDatasource(`cypress-${data.dsName}-aws-textract`); + cy.uninstallMarketplacePlugin("AWS Textract"); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { const Accesskey = Cypress.env("awstextract_access"); const Secretkey = Cypress.env("awstextract_secret"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js index b148f92735..0c505cc417 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/azureBlobStorageHappyPath.cy.js @@ -17,8 +17,8 @@ data.customText = fake.randomSentence; describe("Data source Azure Blob Storage", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRow.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRowHappyPath.cy.skip.js similarity index 95% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRow.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRowHappyPath.cy.skip.js index 05c250d01c..4b03148e9e 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRow.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/baseRowHappyPath.cy.skip.js @@ -20,15 +20,15 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source baserow", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on baserow connection form", () => { + it.skip("Should verify elements on baserow connection form", () => { const Apikey = Cypress.env("baserow_apikey"); cy.get(commonSelectors.globalDataSourceIcon).click(); @@ -78,7 +78,7 @@ describe("Data source baserow", () => { deleteDatasource(`cypress-${data.dsName}-baserow`); }); - it("Should verify the functionality of baserow connection form.", () => { + it.skip("Should verify the functionality of baserow connection form.", () => { const Apikey = Cypress.env("baserow_apikey"); selectAndAddDataSource("databases", baseRowText.baserow, data.dsName); @@ -103,7 +103,7 @@ describe("Data source baserow", () => { deleteDatasource(`cypress-${data.dsName}-baserow`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const baserowTableID = Cypress.env("baserow_tableid"); const baserowRowID = Cypress.env("baserow_rowid"); const Apikey = Cypress.env("baserow_apikey"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js similarity index 98% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js index cee23b0807..24a39a3acc 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js @@ -16,8 +16,8 @@ const data = {}; describe("Data source BigQuery", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js index f221ed3c16..6ac8a3c8d1 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js index 53fab94f67..bb5923ec7b 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js index e16c6d5314..8e4a17d173 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js @@ -21,8 +21,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js index eb9a030963..5ff912d2d8 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data source DynamoDB", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js index c7a1f242fa..88627284ad 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js @@ -17,9 +17,12 @@ import { const data = {}; describe("Data source Elasticsearch", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); - data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); + cy.apiLogin(); + cy.visit("/"); + + data.dataSourceName = fake.lastName + .toLowerCase() + .replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on Elasticsearch connection form", () => { @@ -123,14 +126,14 @@ describe("Data source Elasticsearch", () => { "have.text", elasticsearchText.errorConnectionRefused ); - deleteDatasource(`cypress-${data.lastName}-elasticsearch`); + deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`); }); it("Should verify the functionality of Elasticsearch connection form.", () => { selectAndAddDataSource( "databases", elasticsearchText.elasticSearch, - data.lastName + data.dataSourceName ); fillDataSourceTextField( @@ -210,12 +213,12 @@ describe("Data source Elasticsearch", () => { ); cy.get( - `[data-cy="cypress-${data.lastName}-elasticsearch-button"]` + `[data-cy="cypress-${data.dataSourceName}-elasticsearch-button"]` ).verifyVisibleElement( "have.text", - `cypress-${data.lastName}-elasticsearch` + `cypress-${data.dataSourceName}-elasticsearch` ); - deleteDatasource(`cypress-${data.lastName}-elasticsearch`); + deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js index 6e703fc895..674501b2db 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js @@ -17,8 +17,8 @@ const data = {}; describe("Data source Firestore", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQL.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQLHappyPath.cy.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQL.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQLHappyPath.cy.js index 008121b863..7ad8e5484b 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQL.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/graphQLHappyPath.cy.js @@ -19,12 +19,12 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source GraphQL", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on GraphQL connection form", () => { diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDb.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDbHappyPath.cy.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDb.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDbHappyPath.cy.js index 9ec327cf71..12ad23efd9 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDb.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/harperDbHappyPath.cy.js @@ -20,13 +20,13 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source HarperDB", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on HarperDB connection form", () => { @@ -102,6 +102,7 @@ describe("Data source HarperDB", () => { ); deleteDatasource(`cypress-${data.dsName}-HarperDB`); + cy.uninstallMarketplacePlugin("HarperDB"); }); it("Should verify functionality of HarperDB connection form", () => { @@ -156,9 +157,10 @@ describe("Data source HarperDB", () => { ); deleteDatasource(`cypress-${data.dsName}-HarperDB`); + cy.uninstallMarketplacePlugin("HarperDB"); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const Host = Cypress.env("harperdb_host"); const Port = Cypress.env("harperdb_port"); const Username = Cypress.env("harperdb_username"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js index 36b39572d4..24dc92359c 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js @@ -23,8 +23,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js index 4c6c57d596..58c8c30705 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minio.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minioHappyPath.cy.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minio.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minioHappyPath.cy.js index 7ea22b8693..6572724e3d 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minio.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/minioHappyPath.cy.js @@ -20,12 +20,12 @@ import { import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source minio", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); it("Should verify elements on minio connection form", () => { @@ -157,7 +157,7 @@ describe("Data source minio", () => { deleteDatasource(`cypress-${data.dsName}-minio`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const Host = Cypress.env("minio_host"); const Port = Cypress.env("minio_port"); const AccessKey = Cypress.env("minio_accesskey"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js similarity index 99% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js index 77d2e2ffa4..5729ea18c8 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js @@ -27,8 +27,8 @@ const data = {}; describe("Data source MongoDB", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); @@ -133,7 +133,7 @@ describe("Data source MongoDB", () => { "have.text", mongoDbText.errorConnectionRefused ); - cy.get('[data-cy="query-select-dropdown"]').type( + cy.get('[data-cy="connection-type-select-dropdown"]').type( mongoDbText.optionConnectUsingConnectionString ); cy.get('[data-cy="label-connection-string"]').verifyVisibleElement( diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js similarity index 99% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js index 38e221ba0a..d54e15c933 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js @@ -26,8 +26,8 @@ const data = {}; describe("Data sources MySql", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js index e7ae4f296b..0cd7ea4223 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/oracleDbHappyPath.cy.skip.js @@ -15,7 +15,7 @@ import { describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); + cy.apiLogin(); // cy.createApp(); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js index a6ff7595e5..2da4902edd 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js @@ -20,14 +20,14 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on connection form", () => { + it.skip("Should verify elements on connection form", () => { cy.log(process.env.NODE_ENV); cy.log(postgreSqlText.allDatabase()); cy.get(commonSelectors.globalDataSourceIcon).click(); @@ -140,7 +140,7 @@ describe("Data sources", () => { deleteDatasource(`cypress-${data.dataSourceName}-postgresql`); }); - it("Should verify the functionality of PostgreSQL connection form.", () => { + it.skip("Should verify the functionality of PostgreSQL connection form.", () => { selectAndAddDataSource( "databases", postgreSqlText.postgreSQL, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js index 8217a9db60..ddceea1b52 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/redisHappyPath.cy.js @@ -23,7 +23,7 @@ data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source Redis", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); }); it("Should verify elements on connection Redis form", () => { @@ -215,7 +215,7 @@ describe("Data source Redis", () => { deleteDatasource(`cypress-${data.dsName}-redis`); }); - it("Should able to run the query with valid conection", () => { + it.skip("Should able to run the query with valid conection", () => { selectAndAddDataSource("databases", redisText.redis, data.dsName); fillDataSourceTextField( diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js index 0d45f8af89..825439e687 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/restAPIHappyPath.cy.js @@ -20,7 +20,7 @@ const clientAuthenticationDropdown = describe("Data source Rest API", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); @@ -332,7 +332,6 @@ describe("Data source Rest API", () => { deleteDatasource(`cypress-${data.dataSourceName}-restapi`); }); it("Should verify basic connection for Rest API", () => { - const storedId = Cypress.env("storedId"); cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, `cypress-${data.dataSourceName}-restapi`, diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js index 267eedea1f..abdde2ba00 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js @@ -19,8 +19,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js index 12b3817f16..73ccc703c3 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js @@ -20,7 +20,7 @@ const data = {}; describe("Data sources AWS S3", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js index c240286274..25ec2e4a9c 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sapHanaHappyPath.cy.skip.js @@ -15,7 +15,7 @@ import { describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); + cy.apiLogin(); // cy.createApp(); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js index 4e824aeda5..506fe5d660 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js @@ -13,8 +13,8 @@ const data = {}; describe("Data source SMTP", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js index 4409c0577b..7c1fb5b588 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js @@ -20,8 +20,8 @@ import { const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js similarity index 99% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js index 9501fdabbb..954fb659b5 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js @@ -21,8 +21,8 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); - cy.defaultWorkspaceLogin(); + cy.apiLogin(); + cy.visit("/"); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilio.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilioHappyPath.cy.skip.js similarity index 94% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilio.cy.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilioHappyPath.cy.skip.js index ee2fa9a6e3..4ac2575266 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilio.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/twilioHappyPath.cy.skip.js @@ -21,15 +21,15 @@ import { dataSourceSelector } from "../../../../../constants/selectors/dataSourc import { pluginSelectors } from "Selectors/plugins"; const data = {}; -data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); describe("Data source Twilio", () => { beforeEach(() => { cy.apiLogin(); - cy.defaultWorkspaceLogin(); + cy.visit("/"); + data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); }); - it("Should verify elements on Twilio connection form", () => { + it.skip("Should verify elements on Twilio connection form", () => { const AuthToken = Cypress.env("twilio_auth_token"); const AccountSID = Cypress.env("twilio_account_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID"); @@ -89,7 +89,7 @@ describe("Data source Twilio", () => { deleteDatasource(`cypress-${data.dsName}-twilio`); }); - it("Should verify functionality of Twilio connection form", () => { + it.skip("Should verify functionality of Twilio connection form", () => { const AuthToken = Cypress.env("twilio_auth_token"); const AccountSID = Cypress.env("twilio_account_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID"); @@ -128,7 +128,7 @@ describe("Data source Twilio", () => { deleteDatasource(`cypress-${data.dsName}-twilio`); }); - it("Should be able to run the query with a valid connection", () => { + it.skip("Should be able to run the query with a valid connection", () => { const AuthToken = Cypress.env("twilio_auth_token"); const AccountSID = Cypress.env("twilio_account_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID"); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js index ff15053f09..4b4fe20e62 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/typeSenseHappyPath.cy.skip.js @@ -20,7 +20,7 @@ const data = {}; describe("Data sources", () => { beforeEach(() => { - cy.appUILogin(); + cy.apiLogin(); data.dataSourceName = fake.lastName .toLowerCase() .replaceAll("[^A-Za-z]", ""); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js index e97269cf39..b1f3733c73 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js @@ -46,9 +46,7 @@ describe("App Export", () => { }); it("Verify the elements of export dialog box", () => { - cy.window({ log: false }).then((win) => { - win.localStorage.setItem("walkthroughCompleted", "true"); - }); + cy.skipWalkthrough() cy.apiLogin(); cy.visit(`${data.workspaceSlug}`); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js index a6b068d137..2bd1ccf51e 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appImport.cy.js @@ -34,6 +34,7 @@ describe("App Import Functionality", () => { cy.apiLogin(); cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); cy.apiLogout(); + cy.skipWalkthrough() }); it("should verify app import functionality", () => { @@ -100,12 +101,13 @@ describe("App Import Functionality", () => { .and("have.text", importText.appImportedToastMessage); // Verify imported app - cy.get(".driver-close-btn").click(); + cy.get(commonSelectors.toastCloseButton).click(); cy.wait(500); cy.get(commonSelectors.appNameInput).verifyVisibleElement( "contain.value", "three-versions" ); + cy.get(appVersionSelectors.currentVersionField("v3")).should("be.visible"); // Configure app cy.skipEditorPopover(); 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 b3e3f975dc..b65d54eac6 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 @@ -27,17 +27,21 @@ describe("App Slug", () => { }); it("Verify app slug cases in global settings", () => { - cy.apiLogin(); const workspaceId = Cypress.env("workspaceId"); const appId = Cypress.env("appId"); + const appUrl = `${host}/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`; - cy.visit("/my-workspace"); - cy.wait(1000); + cy.apiLogin(); + cy.skipWalkthrough(); - cy.window({ log: false }).then((win) => { - win.localStorage.setItem("walkthroughCompleted", "true"); + cy.visit(appUrl); + cy.url().then((url) => { + if (url !== appUrl) { + cy.visit(appUrl); + } }); - cy.visit(`/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`); + cy.url().should("eq", appUrl); + cy.wait(1000); cy.get(commonSelectors.leftSideBarSettingsButton).click(); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.skip.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js similarity index 96% rename from cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.skip.js rename to cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js index fdd6acfe80..d6483fa400 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js @@ -78,11 +78,11 @@ describe("Private and Public apps", { // Test private access logout(); - cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); cy.visitSlug({ actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, }); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); cy.wait(2000); cy.appUILogin(); @@ -116,6 +116,9 @@ describe("Private and Public apps", { inviteUserToWorkspace(data.firstName, data.email); logout(); + cy.visit("/"); + cy.wait(2000); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); // Test private access cy.visitSlug({ @@ -141,6 +144,8 @@ describe("Private and Public apps", { cy.wait(1000); cy.apiMakeAppPublic(); logout(); + cy.wait(1000); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); cy.visitSlug({ actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, @@ -177,6 +182,9 @@ describe("Private and Public apps", { cy.apiMakeAppPublic(); logout(); + cy.wait(1000); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); + cy.visitSlug({ actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, }); @@ -229,6 +237,8 @@ describe("Private and Public apps", { cy.get('[data-cy="viewer-page-logo"]').click(); logout(); + cy.wait(1000); + cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); // Setup new workspace and app cy.defaultWorkspaceLogin(); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.skip.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js similarity index 98% rename from cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.skip.js rename to cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js index f97962d910..c0f6064564 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js @@ -123,7 +123,7 @@ describe("App Version", () => { releasedVersionAndVerify("v2"); }); - it.only("should verify version management with components and queries", () => { + it("should verify version management with components and queries", () => { // Initial setup with component and datasource cy.apiAddComponentToApp( data.appName, diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js index 4ccd01274a..17f0872846 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js @@ -80,8 +80,8 @@ describe("Workspace constants", () => { addNewconstants("restapiHeaderKey", "customHeader"); addNewconstants("restapiHeaderValue", "key=value"); addNewconstants("deleteConst", "deleteconst"); - addNewconstants("gconst", "236"); - addNewconstants("gconstUrl", "http://34.66.166.236:4000/"); + addNewconstants("gconst", "108"); + addNewconstants("gconstUrl", "http://20.29.40.108:4000/"); addNewconstants("gconstEndpoint", "production"); // create secret constants @@ -118,6 +118,7 @@ describe("Workspace constants", () => { //Verify all static and datasource queries output in components for (let i = 3; i <= 16; i++) { + cy.log("Verifying textinput" + i); cy.get(commonWidgetSelector.draggableWidget(`textinput${i}`)) .verifyVisibleElement("have.value", "Production environment testing"); } diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.skip.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.skip.js rename to cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/appCreate.cy.js diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.skip.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js similarity index 63% rename from cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.skip.js rename to cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js index 64a05d00c7..fb8e932973 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js @@ -44,6 +44,164 @@ describe("dashboard", () => { cy.visit(`${data.workspaceSlug}`); }); + // it("Should verify app card elements and app card operations", () => { + // const customLayout = { + // desktop: { top: 100, left: 20 }, + // mobile: { width: 8, height: 50 }, + // }; + + // cy.apiCreateApp(data.appName); + // cy.visit(`${data.workspaceSlug}`); + + // cy.wait(2000); + // cy.get(commonSelectors.appCreationDetails).should("be.visible"); + // cy.get(commonSelectors.appCard(data.appName)).should("be.visible"); + // cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement( + // "have.text", + // data.appName + // ); + + // viewAppCardOptions(data.appName); + // cy.get( + // commonSelectors.appCardOptions(commonText.changeIconOption) + // ).verifyVisibleElement("have.text", commonText.changeIconOption); + // cy.get( + // commonSelectors.appCardOptions(commonText.addToFolderOption) + // ).verifyVisibleElement("have.text", commonText.addToFolderOption); + // cy.get( + // commonSelectors.appCardOptions(commonText.cloneAppOption) + // ).verifyVisibleElement("have.text", commonText.cloneAppOption); + // cy.get( + // commonSelectors.appCardOptions(commonText.exportAppOption) + // ).verifyVisibleElement("have.text", commonText.exportAppOption); + // cy.get( + // commonSelectors.appCardOptions(commonText.deleteAppOption) + // ).verifyVisibleElement("have.text", commonText.deleteAppOption); + + // modifyAndVerifyAppCardIcon(data.appName); + // createFolder(data.folderName); + + // viewAppCardOptions(data.appName); + // cy.get( + // commonSelectors.appCardOptions(commonText.addToFolderOption) + // ).click(); + // verifyModal( + // dashboardText.addToFolderTitle, + // dashboardText.addToFolderButton, + // dashboardSelector.selectFolder + // ); + // cy.get(dashboardSelector.moveAppText).verifyVisibleElement( + // "have.text", + // dashboardText.moveAppText(data.appName) + // ); + + // cy.get(dashboardSelector.selectFolder).click(); + // cy.get(commonSelectors.folderList).contains(data.folderName).click(); + // cy.get(dashboardSelector.addToFolderButton).click(); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // commonText.AddedToFolderToast, + // false + // ); + + // cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement( + // "have.text", + // dashboardText.folderName(`${data.folderName} (1)`) + // ); + + // cy.get(dashboardSelector.folderName(data.folderName)).click(); + // cy.get(commonSelectors.appCard(data.appName)) + // .contains(data.appName) + // .should("be.visible"); + + // viewAppCardOptions(data.appName); + + // cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption)) + // .verifyVisibleElement("have.text", commonText.removeFromFolderOption) + // .click(); + // verifyConfirmationModal(commonText.appRemovedFromFolderMessage); + + // cancelModal(commonText.cancelButton); + + // viewAppCardOptions(data.appName); + // cy.get( + // commonSelectors.appCardOptions(commonText.removeFromFolderOption) + // ).click(); + // cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // commonText.appRemovedFromFolderTaost, + // false + // ); + // cy.get(commonSelectors.modalComponent).should("not.exist"); + // cy.get(commonSelectors.empytyFolderImage).should("be.visible"); + // cy.get(commonSelectors.emptyFolderText).verifyVisibleElement( + // "have.text", + // commonText.emptyFolderText + // ); + // cy.get(commonSelectors.allApplicationsLink).click(); + // deleteFolder(data.folderName); + + // cy.get(commonSelectors.allApplicationsLink).click(); + + // cy.wait(1000); + // viewAppCardOptions(data.appName); + // cy.wait(2000); + // cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click(); + // cy.get(commonSelectors.exportAllButton).click(); + + // cy.exec("ls ./cypress/downloads/").then((result) => { + // const downloadedAppExportFileName = result.stdout.split("\n")[0]; + // expect(downloadedAppExportFileName).to.contain.string("app"); + // }); + + // viewAppCardOptions(data.appName); + // cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click(); + // cy.get('[data-cy="clone-app"]').click(); + // cy.get(".go3958317564") + // .should("be.visible") + // .and("have.text", dashboardText.appClonedToast); + // cy.wait(3000); + + // cy.renameApp(data.cloneAppName); + // cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25); + // cy.backToApps(); + // cy.wait("@appLibrary"); + // cy.wait(1000); + + // cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible"); + + // cy.wait(1000); + + // viewAppCardOptions(data.cloneAppName); + // cy.get(commonSelectors.deleteAppOption).click(); + // cy.get(commonSelectors.modalMessage).verifyVisibleElement( + // "have.text", + // commonText.deleteAppModalMessage(data.cloneAppName) + // ); + // cy.get( + // commonSelectors.buttonSelector(commonText.cancelButton) + // ).verifyVisibleElement("have.text", commonText.cancelButton); + // cy.get( + // commonSelectors.buttonSelector(commonText.modalYesButton) + // ).verifyVisibleElement("have.text", commonText.modalYesButton); + // cancelModal(commonText.cancelButton); + + // viewAppCardOptions(data.cloneAppName); + // cy.get(commonSelectors.deleteAppOption).click(); + // cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // commonText.appDeletedToast, + // false + // ); + // verifyAppDelete(data.cloneAppName); + // cy.wait("@appLibrary"); + + // cy.deleteApp(data.appName); + // verifyAppDelete(data.appName); + // }); + it("should verify the elements on empty dashboard", () => { cy.intercept("GET", "/api/metadata", { body: { @@ -171,181 +329,6 @@ describe("dashboard", () => { verifyTooltip(dashboardSelector.modeToggle, "Mode"); }); - it.skip("Should verify app card elements and app card operations", () => { - const customLayout = { - desktop: { top: 100, left: 20 }, - mobile: { width: 8, height: 50 }, - }; - - cy.apiCreateApp(data.appName); - cy.openApp(); - cy.apiAddComponentToApp(data.appName, "text1", customLayout); - - cy.backToApps(); - - cy.wait(500); - cy.get(commonSelectors.appCard(data.appName)) - .parent() - .within(() => { - cy.get(commonSelectors.appCard(data.appName)).should("be.visible"); - cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement( - "have.text", - data.appName - ); - cy.get(commonSelectors.appCreationDetails).should("be.visible"); - - //Add the edited details - }); - - viewAppCardOptions(data.appName); - cy.get( - commonSelectors.appCardOptions(commonText.changeIconOption) - ).verifyVisibleElement("have.text", commonText.changeIconOption); - cy.get( - commonSelectors.appCardOptions(commonText.addToFolderOption) - ).verifyVisibleElement("have.text", commonText.addToFolderOption); - cy.get( - commonSelectors.appCardOptions(commonText.cloneAppOption) - ).verifyVisibleElement("have.text", commonText.cloneAppOption); - cy.get( - commonSelectors.appCardOptions(commonText.exportAppOption) - ).verifyVisibleElement("have.text", commonText.exportAppOption); - cy.get( - commonSelectors.appCardOptions(commonText.deleteAppOption) - ).verifyVisibleElement("have.text", commonText.deleteAppOption); - - modifyAndVerifyAppCardIcon(data.appName); - createFolder(data.folderName); - - viewAppCardOptions(data.appName); - cy.get( - commonSelectors.appCardOptions(commonText.addToFolderOption) - ).click(); - verifyModal( - dashboardText.addToFolderTitle, - dashboardText.addToFolderButton, - dashboardSelector.selectFolder - ); - cy.get(dashboardSelector.moveAppText).verifyVisibleElement( - "have.text", - dashboardText.moveAppText(data.appName) - ); - - cy.get(dashboardSelector.selectFolder).click(); - cy.get(commonSelectors.folderList).contains(data.folderName).click(); - cy.get(dashboardSelector.addToFolderButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.AddedToFolderToast - ); - - cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement( - "have.text", - dashboardText.folderName(`${data.folderName} (1)`) - ); - - cy.get(dashboardSelector.folderName(data.folderName)).click(); - cy.get(commonSelectors.appCard(data.appName)) - .contains(data.appName) - .should("be.visible"); - - cy.wait(2000); - viewAppCardOptions(data.appName); - - cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption)) - .verifyVisibleElement("have.text", commonText.removeFromFolderOption) - .click(); - verifyConfirmationModal(commonText.appRemovedFromFolderMessage); - - cancelModal(commonText.cancelButton); - - cy.wait(3000); - viewAppCardOptions(data.appName); - cy.get( - commonSelectors.appCardOptions(commonText.removeFromFolderOption) - ).click(); - cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.appRemovedFromFolderTaost - ); - cy.get(commonSelectors.modalComponent).should("not.exist"); - cy.get(commonSelectors.empytyFolderImage).should("be.visible"); - cy.get(commonSelectors.emptyFolderText).verifyVisibleElement( - "have.text", - commonText.emptyFolderText - ); - cy.get(commonSelectors.allApplicationsLink).click(); - deleteFolder(data.folderName); - - cy.get(commonSelectors.allApplicationsLink).click(); - - cy.wait(3000); - viewAppCardOptions(data.appName); - cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click(); - cy.get('[data-cy="clone-app"]').click(); - cy.get(".go3958317564") - .should("be.visible") - .and("have.text", dashboardText.appClonedToast); - cy.wait(3000); - cy.renameApp(data.cloneAppName); - cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25); - cy.backToApps(); - cy.wait("@appLibrary"); - cy.wait(1000); - cy.reloadAppForTheElement(data.cloneAppName); - - cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible"); - - cy.get(commonSelectors.globalDataSourceIcon).click(); - cy.get(commonSelectors.dashboardIcon).click(); - cy.wait(3000); - cy.reloadAppForTheElement(data.cloneAppName); - viewAppCardOptions(data.cloneAppName); - cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click(); - cy.get(commonSelectors.exportAllButton).click(); - - cy.exec("ls ./cypress/downloads/").then((result) => { - const downloadedAppExportFileName = result.stdout.split("\n")[0]; - expect(downloadedAppExportFileName).to.contain.string("app"); - }); - - cy.wait(3000); - cy.reloadAppForTheElement(data.cloneAppName); - viewAppCardOptions(data.cloneAppName); - cy.get(commonSelectors.deleteAppOption).click(); - cy.get(commonSelectors.modalMessage).verifyVisibleElement( - "have.text", - commonText.deleteAppModalMessage(data.cloneAppName) - ); - cy.get( - commonSelectors.buttonSelector(commonText.cancelButton) - ).verifyVisibleElement("have.text", commonText.cancelButton); - cy.get( - commonSelectors.buttonSelector(commonText.modalYesButton) - ).verifyVisibleElement("have.text", commonText.modalYesButton); - cancelModal(commonText.cancelButton); - - cy.wait(3000); - cy.reloadAppForTheElement(data.cloneAppName); - viewAppCardOptions(data.cloneAppName); - cy.get(commonSelectors.deleteAppOption).click(); - cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.appDeletedToast - ); - verifyAppDelete(data.cloneAppName); - cy.wait("@appLibrary"); - - cy.deleteApp(data.appName); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.appDeletedToast - ); - verifyAppDelete(data.appName); - }); - it("Should verify the app CRUD operation", () => { const customLayout = { desktop: { top: 100, left: 20 }, @@ -369,10 +352,7 @@ describe("dashboard", () => { cy.wait("@appLibrary"); cy.deleteApp(data.appName); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.appDeletedToast - ); + verifyAppDelete(data.appName); }); @@ -493,10 +473,7 @@ describe("dashboard", () => { cy.get(commonSelectors.allApplicationsLink).click(); cy.deleteApp(data.appName); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.appDeletedToast - ); + verifyAppDelete(data.appName); logout(); }); 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 d69cf77689..96b87dd9b7 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 @@ -78,14 +78,16 @@ describe("Manage Groups", () => { cy.createApp(data.appName); cy.verifyToastMessage( commonSelectors.toastMessage, - commonText.appCreatedToast + commonText.appCreatedToast, + false ); cy.backToApps(); cy.deleteApp(data.appName); cy.verifyToastMessage( commonSelectors.toastMessage, - commonText.appDeletedToast + commonText.appDeletedToast, + false ); // Folder operations @@ -115,7 +117,8 @@ describe("Manage Groups", () => { cy.get(commonSelectors.cloneAppButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - dashboardText.appClonedToast + dashboardText.appClonedToast, + false ); // cy.get(commonSelectors.cancelButton).click(); cy.apiLogout(); @@ -177,14 +180,16 @@ describe("Manage Groups", () => { cy.createApp(data.appName); cy.verifyToastMessage( commonSelectors.toastMessage, - commonText.appCreatedToast + commonText.appCreatedToast, + false ); cy.backToApps(); cy.deleteApp(data.appName); cy.verifyToastMessage( commonSelectors.toastMessage, - commonText.appDeletedToast + commonText.appDeletedToast, + false ); // Folder operations 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 22b6c6b6a5..bfa5806939 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 @@ -204,10 +204,7 @@ describe("Manage Groups", () => { cy.wait(2500); cy.deleteApp(data.appName); - cy.verifyToastMessage( - commonSelectors.toastMessage, - commonText.appDeletedToast - ); + // Folder operations createFolder(data.folderName); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/eeTestcases/externalApi/apiUsers.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js rename to cypress-tests/cypress/e2e/happyPath/platform/eeTestcases/externalApi/apiUsers.cy.js diff --git a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/appImportAndExportAPI.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/eeTestcases/externalApi/appImportAndExportAPI.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/happyPath/platform/externalApi/appImportAndExportAPI.cy.js rename to cypress-tests/cypress/e2e/happyPath/platform/eeTestcases/externalApi/appImportAndExportAPI.cy.js diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js rename to cypress-tests/cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js diff --git a/cypress-tests/cypress/fixtures/templates/invalid_app.json b/cypress-tests/cypress/fixtures/templates/invalid_app.json index e2ebed4bce..edb4c882a3 100644 --- a/cypress-tests/cypress/fixtures/templates/invalid_app.json +++ b/cypress-tests/cypress/fixtures/templates/invalid_app.json @@ -2127,7 +2127,7 @@ "encrypted": false }, "host": { - "value": "35.238.9.114", + "value": "9.234.17.31", "encrypted": false }, "port": { diff --git a/cypress-tests/cypress/fixtures/templates/one_version.json b/cypress-tests/cypress/fixtures/templates/one_version.json index 08dad1d1c0..a93a35900e 100644 --- a/cypress-tests/cypress/fixtures/templates/one_version.json +++ b/cypress-tests/cypress/fixtures/templates/one_version.json @@ -585,7 +585,7 @@ "encrypted": false }, "host": { - "value": "35.238.9.114", + "value": "9.234.17.31", "encrypted": false }, "port": { diff --git a/cypress-tests/cypress/fixtures/templates/three-versions.json b/cypress-tests/cypress/fixtures/templates/three-versions.json index bff312ac54..715f5e68cc 100644 --- a/cypress-tests/cypress/fixtures/templates/three-versions.json +++ b/cypress-tests/cypress/fixtures/templates/three-versions.json @@ -1862,7 +1862,7 @@ "encrypted": false }, "host": { - "value": "35.238.9.114", + "value": "9.234.17.31", "encrypted": false }, "port": { diff --git a/cypress-tests/cypress/fixtures/templates/workspace_constants.json b/cypress-tests/cypress/fixtures/templates/workspace_constants.json index c5c015e832..074e6e408b 100644 --- a/cypress-tests/cypress/fixtures/templates/workspace_constants.json +++ b/cypress-tests/cypress/fixtures/templates/workspace_constants.json @@ -2766,7 +2766,7 @@ "name": "restapiStaticUrlG", "options": { "method": "get", - "url": "http://34.66.166.236:4000/{{constants.gconstEndpoint}}", + "url": "http://20.29.40.108:4000/{{constants.gconstEndpoint}}", "url_params": [ [ "", @@ -2814,7 +2814,7 @@ "name": "restapiUrlS", "options": { "method": "get", - "url": "http://34.66.166.236:4000/{{secrets.sconstEndpoint}}", + "url": "http://20.29.40.108:4000/{{secrets.sconstEndpoint}}", "url_params": [ [ "", @@ -2908,7 +2908,7 @@ "name": "restapiUrlGS", "options": { "method": "get", - "url": "http://34.66.166.{{constants.gconst}}{{secrets.sconst}}/production", + "url": "http://20.29.40.{{constants.gconst}}{{secrets.sconst}}/production", "url_params": [ [ "", @@ -3419,7 +3419,7 @@ "environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c", "options": { "url": { - "value": "http://34.66.166.236:4000/{{constants.gconstEndpoint}}", + "value": "http://20.29.40.108:4000/{{constants.gconstEndpoint}}", "encrypted": false }, "auth_type": { @@ -3540,7 +3540,7 @@ "environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c", "options": { "url": { - "value": "http://34.66.166.236:4000/{{secrets.sconstEndpoint}}", + "value": "http://20.29.40.108:4000/{{secrets.sconstEndpoint}}", "encrypted": false }, "auth_type": { @@ -3782,7 +3782,7 @@ "environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c", "options": { "url": { - "value": "http://34.66.166.{{constants.gconst}}{{secrets.sconst}}/production", + "value": "http://20.29.40.{{constants.gconst}}{{secrets.sconst}}/production", "encrypted": false }, "auth_type": { diff --git a/cypress-tests/cypress/support/utils/common.js b/cypress-tests/cypress/support/utils/common.js index 0f54553472..7a8c55ffcf 100644 --- a/cypress-tests/cypress/support/utils/common.js +++ b/cypress-tests/cypress/support/utils/common.js @@ -101,11 +101,14 @@ export const navigateToAppEditor = (appName) => { export const viewAppCardOptions = (appName) => { cy.wait(1000); - cy.reloadAppForTheElement(appName); + cy.get(commonSelectors.appCard(appName)) + .realHover() + .find(commonSelectors.appCardOptionsButton) + .realHover() cy.contains("div", appName) .parent() .within(() => { - cy.get(commonSelectors.appCardOptionsButton).invoke("click"); + cy.get(commonSelectors.appCardOptionsButton).click(); }); }; @@ -185,8 +188,9 @@ export const searchUser = (email) => { }; export const selectAppCardOption = (appName, appCardOption) => { + cy.wait(1000); viewAppCardOptions(appName); - cy.get(appCardOption).should("be.visible").click({ force: true }); + cy.get(appCardOption).should("be.visible").click(); }; export const navigateToDatabase = () => { diff --git a/cypress-tests/cypress/support/utils/dashboard.js b/cypress-tests/cypress/support/utils/dashboard.js index 0809909297..dbc090735b 100644 --- a/cypress-tests/cypress/support/utils/dashboard.js +++ b/cypress-tests/cypress/support/utils/dashboard.js @@ -53,6 +53,8 @@ export const modifyAndVerifyAppCardIcon = (appName) => { }; export const verifyAppDelete = (appName) => { + cy.get("body").should("exist").and("be.visible"); + cy.get('[data-cy="dashboard-section-header"]').should("be.visible"); cy.get("body").then(($title) => { if (!$title.text().includes(commonText.introductionMessage)) { cy.clearAndType(commonSelectors.homePageSearchBar, appName); diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js index 6f17004409..4c1446636e 100644 --- a/cypress-tests/cypress/support/utils/dataSource.js +++ b/cypress-tests/cypress/support/utils/dataSource.js @@ -239,7 +239,8 @@ export const createRestAPIQuery = ( key = "", value = "", url = "", - run = true + run = true, + kind = "restapi" ) => { cy.getCookie("tj_auth_token").then((cookie) => { const headers = { @@ -247,7 +248,6 @@ export const createRestAPIQuery = ( Cookie: `tj_auth_token=${cookie.value}`, }; - cy.log(Cypress.env("appId")); cy.request({ method: "GET", url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`, @@ -255,13 +255,13 @@ export const createRestAPIQuery = ( }).then((response) => { const editingVersionId = response.body.editing_version.id; - const data_source_id = Cypress.env(`${dsName}-id`); + const data_source_id = Cypress.env(kind); const requestBody = { app_id: Cypress.env("appId"), app_version_id: editingVersionId, name: queryName, - kind: "restapi", + kind: kind, options: { method: "get", url: url, diff --git a/cypress-tests/cypress/support/utils/restAPI.js b/cypress-tests/cypress/support/utils/restAPI.js index f5a44e3fd5..2dfc225a65 100644 --- a/cypress-tests/cypress/support/utils/restAPI.js +++ b/cypress-tests/cypress/support/utils/restAPI.js @@ -18,79 +18,97 @@ export const createAndRunRestAPIQuery = ( method: "GET", url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`, headers, - }).then((response) => { - const editingVersionId = response.body.editing_version.id; - const data_source_id = Cypress.env(`${dsName}-id`); - const useJsonBody = - ["POST", "PATCH", "PUT"].includes(method.toUpperCase()) && - jsonBody !== null; - - const queryOptions = { - method: method.toLowerCase(), - url: url + urlSuffix, - url_params: [["", ""]], - headers: headersList.length ? headersList : [["", ""]], - body: !useJsonBody && bodyList.length ? bodyList : [["", ""]], - json_body: useJsonBody ? jsonBody : null, - body_toggle: useJsonBody, - runOnPageLoad: run, - transformationLanguage: "javascript", - enableTransformation: false, - }; - - const requestBody = { - app_id: Cypress.env("appId"), - app_version_id: editingVersionId, - name: queryName, - kind: "restapi", - options: queryOptions, - data_source_id, - plugin_id: null, - }; + }).then((appResponse) => { + const currentEnvironmentId = appResponse.body.editorEnvironment.id; + const editingVersionId = appResponse.body.editing_version.id; cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`, + method: "GET", + url: `${Cypress.env("server_host")}/api/data-sources/${Cypress.env("workspaceId")}/environments/${currentEnvironmentId}/versions/${editingVersionId}`, headers, - body: requestBody, - }).then((createResponse) => { - expect(createResponse.status).to.equal(201); - const queryId = createResponse.body.id; - cy.log("Query created successfully:", queryId); + }).then((dsResponse) => { + expect(dsResponse.status).to.eq(200); - const createdOptions = createResponse.body.options; - expect(createdOptions.method).to.equal(queryOptions.method); - expect(createdOptions.url).to.equal(queryOptions.url); - expect(createdOptions.headers).to.deep.equal(queryOptions.headers); + const dataSource = dsResponse.body.data_sources.find( + (ds) => ds.name === dsName + ); - if (useJsonBody) { - expect(createdOptions.json_body).to.deep.equal( - queryOptions.json_body - ); - expect(createdOptions.body_toggle).to.equal(true); - } else { - expect(createdOptions.body).to.deep.equal(queryOptions.body); - expect(createdOptions.body_toggle).to.equal(false); + if (!dataSource) { + throw new Error(`Data source '${dsName}' not found.`); } - expect(createdOptions.runOnPageLoad).to.equal(run); - cy.log("Metadata verified successfully"); - if (run) { - cy.request({ - method: "POST", - url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`, - headers, - }).then((runResponse) => { - expect([200, 201]).to.include(runResponse.status); - cy.log("Query executed successfully:", runResponse.body); - if (runResponse.body?.data.id) { - cy.writeFile("cypress/fixtures/restAPI/storedId.json", { - id: runResponse.body.data.id, - }); - cy.log("Stored ID:", runResponse.body.data.id); - } - }); - } + const data_source_id = dataSource.id; + const useJsonBody = + ["POST", "PATCH", "PUT"].includes(method.toUpperCase()) && + jsonBody !== null; + + const queryOptions = { + method: method.toLowerCase(), + url: url + urlSuffix, + url_params: [["", ""]], + headers: headersList.length ? headersList : [["", ""]], + body: !useJsonBody && bodyList.length ? bodyList : [["", ""]], + json_body: useJsonBody ? jsonBody : null, + body_toggle: useJsonBody, + runOnPageLoad: run, + transformationLanguage: "javascript", + enableTransformation: false, + }; + + const requestBody = { + app_id: Cypress.env("appId"), + app_version_id: editingVersionId, + name: queryName, + kind: "restapi", + options: queryOptions, + data_source_id, + plugin_id: null, + }; + + cy.request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`, + headers, + body: requestBody, + }).then((createResponse) => { + expect(createResponse.status).to.equal(201); + const queryId = createResponse.body.id; + cy.log("Query created successfully:", queryId); + + const createdOptions = createResponse.body.options; + expect(createdOptions.method).to.equal(queryOptions.method); + expect(createdOptions.url).to.equal(queryOptions.url); + expect(createdOptions.headers).to.deep.equal(queryOptions.headers); + + if (useJsonBody) { + expect(createdOptions.json_body).to.deep.equal( + queryOptions.json_body + ); + expect(createdOptions.body_toggle).to.equal(true); + } else { + expect(createdOptions.body).to.deep.equal(queryOptions.body); + expect(createdOptions.body_toggle).to.equal(false); + } + + expect(createdOptions.runOnPageLoad).to.equal(run); + cy.log("Metadata verified successfully"); + if (run) { + cy.request({ + method: "POST", + url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`, + headers, + }).then((runResponse) => { + expect([200, 201]).to.include(runResponse.status); + cy.log("Query executed successfully:", runResponse.body); + if (runResponse.body?.data.id) { + cy.writeFile("cypress/fixtures/restAPI/storedId.json", { + id: runResponse.body.data.id, + }); + cy.log("Stored ID:", runResponse.body.data.id); + } + }); + } + }); }); }); }); diff --git a/cypress-tests/cypress/support/utils/version.js b/cypress-tests/cypress/support/utils/version.js index 77b7d8b0e8..b15d84f45b 100644 --- a/cypress-tests/cypress/support/utils/version.js +++ b/cypress-tests/cypress/support/utils/version.js @@ -115,8 +115,8 @@ export const verifyDuplicateVersion = (newVersion = [], version) => { cy.get(appVersionSelectors.createNewVersionButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, - // appVersionText.versionNameAlreadyExists - "Already exists!" + appVersionText.versionNameAlreadyExists + // "Already exists!" ); }; diff --git a/docker/ce-preview.Dockerfile b/docker/ce-preview.Dockerfile index d71e6c1dbc..0c481e13a3 100644 --- a/docker/ce-preview.Dockerfile +++ b/docker/ce-preview.Dockerfile @@ -38,7 +38,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin ENV NODE_ENV=production ENV NODE_OPTIONS="--max-old-space-size=4096" -RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget supervisor +RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor # Install Instantclient Basic Light Oracle and Dependencies WORKDIR /opt/oracle @@ -54,9 +54,6 @@ ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21 WORKDIR / -RUN mkdir -p /app /var/log/supervisor -COPY /deploy/docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf - # copy npm scripts COPY --from=builder /app/package.json ./app/package.json # copy plugins dependencies @@ -77,32 +74,64 @@ COPY --from=builder /app/server/dist ./app/server/dist WORKDIR /app +# Install PostgreSQL USER root RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list -RUN echo "deb http://deb.debian.org/debian" RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor -USER postgres -RUN service postgresql start && \ - psql -c "create role tooljet with login superuser password 'postgres';" -USER root + +# Explicitly create PG main directory with correct ownership +RUN mkdir -p /var/lib/postgresql/13/main && \ + chown -R postgres:postgres /var/lib/postgresql + +RUN mkdir -p /var/log/supervisor /var/run/postgresql && \ + chown -R postgres:postgres /var/run/postgresql /var/log/supervisor + +# Remove existing data and create directory with proper ownership +RUN rm -rf /var/lib/postgresql/13/main && \ + mkdir -p /var/lib/postgresql/13/main && \ + chown -R postgres:postgres /var/lib/postgresql + +# Initialize PostgreSQL +RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main" + +# Configure Supervisor to manage PostgREST, ToolJet, and Redis +RUN echo "[supervisord] \n" \ + "nodaemon=true \n" \ + "user=root \n" \ + "\n" \ + "[program:postgrest] \n" \ + "command=/bin/postgrest \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "\n" \ + "[program:tooljet] \n" \ + "user=root \n" \ + "command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "stderr_logfile=/dev/stdout \n" \ + "stderr_logfile_maxbytes=0 \n" \ + "stdout_logfile=/dev/stdout \n" \ + "stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf # ENV defaults ENV TOOLJET_HOST=http://localhost \ + PORT=80 \ NODE_ENV=production \ LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ SECRET_KEY_BASE=replace_with_secret_key_base \ PG_DB=tooljet_production \ - PG_USER=tooljet \ + PG_USER=postgres \ PG_PASS=postgres \ PG_HOST=localhost \ ENABLE_TOOLJET_DB=true \ TOOLJET_DB_HOST=localhost \ - TOOLJET_DB_USER=tooljet \ + TOOLJET_DB_USER=postgres \ TOOLJET_DB_PASS=postgres \ TOOLJET_DB=tooljet_db \ PGRST_HOST=http://localhost:3000 \ - PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \ + PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \ PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ PGRST_DB_PRE_CONFIG=postgrest.pre_config \ ORM_LOGGING=true \ @@ -110,4 +139,7 @@ ENV TOOLJET_HOST=http://localhost \ HOME=/home/appuser \ TERM=xterm -CMD ["/usr/bin/supervisord"] + +RUN chmod +x ./server/scripts/preview.sh +# Set the entrypoint +ENTRYPOINT ["./server/scripts/preview.sh"] diff --git a/docker/ee/ee-preview.Dockerfile b/docker/ee/ee-preview.Dockerfile index 91e0864bd0..77ecbff29d 100644 --- a/docker/ee/ee-preview.Dockerfile +++ b/docker/ee/ee-preview.Dockerfile @@ -9,7 +9,7 @@ WORKDIR /app ARG CUSTOM_GITHUB_TOKEN ARG BRANCH_NAME -# Clone and checkout the frontend repository +# Clone and checkout the frontend repositorys RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" RUN git config --global http.version HTTP/1.1 @@ -66,7 +66,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin ENV NODE_ENV=production ENV TOOLJET_EDITION=ee ENV NODE_OPTIONS="--max-old-space-size=4096" -RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget supervisor +RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor # Install Instantclient Basic Light Oracle and Dependencies WORKDIR /opt/oracle @@ -82,9 +82,6 @@ ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21 WORKDIR / -RUN mkdir -p /app /var/log/supervisor -COPY /deploy/docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf - # copy npm scripts COPY --from=builder /app/package.json ./app/package.json # copy plugins dependencies @@ -106,38 +103,73 @@ COPY --from=builder /app/server/dist ./app/server/dist WORKDIR /app -# ENV defaults +# Install PostgreSQL USER root RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list -RUN echo "deb http://deb.debian.org/debian" -RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor -USER postgres -RUN service postgresql start && \ - psql -c "create role tooljet with login superuser password 'postgres';" -USER root +RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor --fix-missing + + +# Explicitly create PG main directory with correct ownership +RUN mkdir -p /var/lib/postgresql/13/main && \ + chown -R postgres:postgres /var/lib/postgresql + +RUN mkdir -p /var/log/supervisor /var/run/postgresql && \ + chown -R postgres:postgres /var/run/postgresql /var/log/supervisor + +# Remove existing data and create directory with proper ownership +RUN rm -rf /var/lib/postgresql/13/main && \ + mkdir -p /var/lib/postgresql/13/main && \ + chown -R postgres:postgres /var/lib/postgresql + +# Initialize PostgreSQL +RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main" + +# Configure Supervisor to manage PostgREST, ToolJet, and Redis +RUN echo "[supervisord] \n" \ + "nodaemon=true \n" \ + "user=root \n" \ + "\n" \ + "[program:postgrest] \n" \ + "command=/bin/postgrest \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "\n" \ + "[program:tooljet] \n" \ + "user=root \n" \ + "command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "stderr_logfile=/dev/stdout \n" \ + "stderr_logfile_maxbytes=0 \n" \ + "stdout_logfile=/dev/stdout \n" \ + "stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf # ENV defaults ENV TOOLJET_HOST=http://localhost \ + PORT=80 \ NODE_ENV=production \ LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ SECRET_KEY_BASE=replace_with_secret_key_base \ PG_DB=tooljet_production \ - PG_USER=tooljet \ + PG_USER=postgres \ PG_PASS=postgres \ PG_HOST=localhost \ ENABLE_TOOLJET_DB=true \ TOOLJET_DB_HOST=localhost \ - TOOLJET_DB_USER=tooljet \ + TOOLJET_DB_USER=postgres \ TOOLJET_DB_PASS=postgres \ TOOLJET_DB=tooljet_db \ PGRST_HOST=http://localhost:3000 \ - PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \ + PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \ PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ PGRST_DB_PRE_CONFIG=postgrest.pre_config \ ORM_LOGGING=true \ DEPLOYMENT_PLATFORM=docker:local \ - REDIS_PASS= \ + HOME=/home/appuser \ TERM=xterm -CMD ["/usr/bin/supervisord"] + +RUN chmod +x ./server/scripts/preview.sh +# Set the entrypoint +ENTRYPOINT ["./server/scripts/preview.sh"] diff --git a/frontend/.version b/frontend/.version index afad818663..171a6a93d6 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.11.0 +3.12.1 diff --git a/frontend/ee b/frontend/ee index 518f3334b1..1b77a55670 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 518f3334b12a83785fd37dd53b0245d72848211a +Subproject commit 1b77a556709211daed8924821383db9dccc95eb5 diff --git a/frontend/package.json b/frontend/package.json index 45d94532f5..3821a370f5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,6 +58,7 @@ "dotenv": "^16.0.3", "draft-js": "^0.11.7", "draft-js-export-html": "^1.4.1", + "draft-js-import-html": "^1.4.1", "driver.js": "^0.9.8", "emoji-mart": "^5.5.2", "file-loader": "^6.2.0", diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 913d3a22df..5f362ba0b3 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -232,6 +232,7 @@ export const getAllChildComponents = (allComponents, parentId) => { const childTabId = componentParentId.split('-').at(-1); if (componentParentId === `${parentId}-${childTabId}`) { childComponent.isParentTabORCalendar = true; + childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId); childComponents.push(childComponent); // Recursively find children of the current child component const childrenOfChild = getAllChildComponents(allComponents, componentId); @@ -242,6 +243,7 @@ export const getAllChildComponents = (allComponents, parentId) => { if (componentParentId === parentId) { let childComponent = deepClone(allComponents[componentId]); childComponent.id = componentId; + childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId); childComponents.push(childComponent); // Recursively find children of the current child component diff --git a/frontend/src/AppBuilder/CodeEditor/CodehinterOverlayTriggers.jsx b/frontend/src/AppBuilder/CodeEditor/CodehinterOverlayTriggers.jsx new file mode 100644 index 0000000000..c79c473169 --- /dev/null +++ b/frontend/src/AppBuilder/CodeEditor/CodehinterOverlayTriggers.jsx @@ -0,0 +1,27 @@ +/* eslint-disable import/no-unresolved */ +import React from 'react'; +import { openSearchPanel } from '@codemirror/search'; +import './SearchBox.scss'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button.jsx'; + +export const CodeHinterBtns = ({ view, isPanelOpen, renderCopilot }) => { + return ( +
+ {!isPanelOpen && ( + openSearchPanel(view)} + /> + )} + {renderCopilot && renderCopilot()} +
+ ); +}; diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx index cbebcb0425..98af1dc9e4 100644 --- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx @@ -20,10 +20,12 @@ import { PreviewBox } from './PreviewBox'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { syntaxTree } from '@codemirror/language'; import { search, searchKeymap, searchPanelOpen } from '@codemirror/search'; -import { handleSearchPanel, SearchBtn } from './SearchBox'; +import { handleSearchPanel } from './SearchBox'; import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks'; import { isInsideParent } from './utils'; +import { CodeHinterBtns } from './CodehinterOverlayTriggers'; const langSupport = Object.freeze({ javascript: javascript(), @@ -66,7 +68,7 @@ const MultiLineCodeEditor = (props) => { const context = useContext(CodeHinterContext); - const { suggestionList } = createReferencesLookup(context, true); + const { suggestionList: paramList } = createReferencesLookup(context, true); const currentValueRef = useRef(initialValue); @@ -74,6 +76,7 @@ const MultiLineCodeEditor = (props) => { const [editorView, setEditorView] = React.useState(null); + const [isSearchPanelOpen, setIsSearchPanelOpen] = React.useState(false); const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline'); const handleOnBlur = () => { @@ -146,8 +149,29 @@ const MultiLineCodeEditor = (props) => { return suggestion.hint.includes(nearestSubstring); }); + const localVariables = new Set(); + + // Traverse the syntax tree to extract variable declarations + syntaxTree(context.state).iterate({ + enter: (node) => { + // JavaScript: Detect variable declarations (var, let, const) + if (node.name === 'VariableDefinition') { + const varName = context.state.sliceDoc(node.from, node.to); + if (varName && varName.startsWith(nearestSubstring)) localVariables.add(varName); + } + }, + }); + + // Convert Set to an array of completion suggestions + const localVariableSuggestions = [...localVariables].map((varName) => ({ + hint: varName, + type: 'variable', + })); + + const suggestionList = paramList.filter((paramSuggestion) => paramSuggestion.hint.includes(nearestSubstring)); + const suggestions = generateHints( - [...JSLangHints, ...autoSuggestionList, ...suggestionList], + [...localVariableSuggestions, ...JSLangHints, ...autoSuggestionList, ...suggestionList], null, nearestSubstring ).map((hint) => { @@ -204,6 +228,7 @@ const MultiLineCodeEditor = (props) => { return { from: context.pos, options: [...suggestions], + filter: false, }; } @@ -237,7 +262,7 @@ const MultiLineCodeEditor = (props) => { ]); // eslint-disable-next-line react-hooks/exhaustive-deps - const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), []); + const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [paramList]); const { handleTogglePopupExapand, isOpen, setIsOpen, forceUpdate } = portalProps; let cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : props.cyLabel; @@ -258,7 +283,7 @@ const MultiLineCodeEditor = (props) => { ref={wrapperRef} >
- + { isMultiEditor={true} isQueryManager={isInsideQueryPane} /> - {renderCopilot && renderCopilot()} { readOnly={readOnly} editable={editable} //for transformations in query manager onCreateEditor={(view) => setEditorView(view)} - onUpdate={(view) => { - const icon = document.querySelector('.codehinter-search-btn'); - if (searchPanelOpen(view.state)) { - icon.style.display = 'none'; - } else icon.style.display = 'block'; - }} + onUpdate={(view) => setIsSearchPanelOpen(searchPanelOpen(view.state))} />
{showPreview && ( diff --git a/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx b/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx index 28f7451b95..140ff2a7db 100644 --- a/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SearchBox.jsx @@ -9,7 +9,6 @@ import { findPrevious, replaceNext, replaceAll, - openSearchPanel, } from '@codemirror/search'; import './SearchBox.scss'; import InputComponent from '@/components/ui/Input/Index.jsx'; @@ -162,22 +161,3 @@ function SearchPanel({ view }) { ); } - -export const SearchBtn = ({ view }) => { - return ( -
- openSearchPanel(view)} - /> -
- ); -}; diff --git a/frontend/src/AppBuilder/CodeEditor/SearchBox.scss b/frontend/src/AppBuilder/CodeEditor/SearchBox.scss index 24c948b0ee..79de28f28a 100644 --- a/frontend/src/AppBuilder/CodeEditor/SearchBox.scss +++ b/frontend/src/AppBuilder/CodeEditor/SearchBox.scss @@ -44,7 +44,5 @@ } .code-hinter-wrapper .codehinter-search-btn { - display: block; - padding-top: 1px; - z-index: 10000; + z-index: 1000; } \ No newline at end of file diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 65e6f2eadd..16514ca409 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -1,12 +1,18 @@ /* eslint-disable import/no-unresolved */ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState, useContext } from 'react'; import { PreviewBox } from './PreviewBox'; import { ToolTip } from '@/Editor/Inspector/Elements/Components/ToolTip'; import { useTranslation } from 'react-i18next'; import { camelCase, isEmpty, noop, get } from 'lodash'; import CodeMirror from '@uiw/react-codemirror'; import { javascript } from '@codemirror/lang-javascript'; -import { autocompletion, completionKeymap, completionStatus, acceptCompletion } from '@codemirror/autocomplete'; +import { + autocompletion, + completionKeymap, + completionStatus, + acceptCompletion, + startCompletion, +} from '@codemirror/autocomplete'; import { defaultKeymap } from '@codemirror/commands'; import { keymap } from '@codemirror/view'; import FxButton from '../CodeBuilder/Elements/FxButton'; @@ -22,6 +28,8 @@ import CodeHinter from './CodeHinter'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext'; +import { createReferencesLookup } from '@/_stores/utils'; import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { @@ -73,6 +81,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r if (typeof initialValue === 'string' && (initialValue?.includes('components') || initialValue?.includes('queries'))) { newInitialValue = replaceIdsWithName(initialValue); } + //! Re render the component when the componentName changes as the initialValue is not updated // const { variablesExposedForPreview } = useContext(EditorContext) || {}; @@ -199,9 +208,14 @@ const EditorInput = ({ wrapperRef, showSuggestions, }) => { - const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow); + const codeHinterContext = useContext(CodeHinterContext); + const { suggestionList: paramHints } = createReferencesLookup(codeHinterContext, true); const getSuggestions = useStore((state) => state.getSuggestions, shallow); + const [codeMirrorView, setCodeMirrorView] = useState(undefined); + + const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow); + const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline'); const isInsideQueryManager = useMemo( @@ -209,16 +223,16 @@ const EditorInput = ({ [wrapperRef.current] ); function autoCompleteExtensionConfig(context) { - const hints = getSuggestions(); + const hintsWithoutParamHints = getSuggestions(); const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager); - const allHints = { - ...hints, - appHints: [...hints.appHints, ...serverHints], - }; - let word = context.matchBefore(/\w*/); + const hints = { + ...hintsWithoutParamHints, + appHints: [...hintsWithoutParamHints.appHints, ...serverHints, ...paramHints], + }; + const totalReferences = (context.state.doc.toString().match(/{{/g) || []).length; let queryInput = context.state.doc.toString(); @@ -247,17 +261,18 @@ const EditorInput = ({ queryInput = '{{' + currentWord + '}}'; } - let completions = getAutocompletion(queryInput, validationType, allHints, totalReferences, originalQueryInput); + let completions = getAutocompletion(queryInput, validationType, hints, totalReferences, originalQueryInput); return { from: word.from, options: completions, validFor: /^\{\{.*\}\}$/, + filter: false, }; } // eslint-disable-next-line react-hooks/exhaustive-deps - const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager]); + const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager, paramHints]); const autoCompleteConfig = autocompletion({ override: [overRideFunction], @@ -424,6 +439,9 @@ const EditorInput = ({ ref={previewRef} > { + setCodeMirrorView(view); + }} value={currentValue} placeholder={placeholder} height={isInsideQueryPane ? '100%' : showLineNumbers ? '400px' : '100%'} @@ -460,11 +478,16 @@ const EditorInput = ({ theme={theme} indentWithTab={false} readOnly={disabled} + onKeyDown={(event) => { + if (event.key === 'Backspace') { + startCompletion(codeMirrorView); + } + }} /> - - - + + + ); }; diff --git a/frontend/src/AppBuilder/CodeEditor/autocompleteExtensionConfig.js b/frontend/src/AppBuilder/CodeEditor/autocompleteExtensionConfig.js index e1d597c957..d845fa521e 100644 --- a/frontend/src/AppBuilder/CodeEditor/autocompleteExtensionConfig.js +++ b/frontend/src/AppBuilder/CodeEditor/autocompleteExtensionConfig.js @@ -67,7 +67,8 @@ export const getAutocompletion = (input, fieldType, hints, totalReferences = 1, originalQueryInput, searchInput ); - return orderSuggestions(suggestions, fieldType); + + return suggestions; }; function orderSuggestions(suggestions, validationType) { @@ -90,10 +91,18 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) => const hasDepth = currentWord.includes('.'); const lastDepth = getLastSubstring(currentWord); - const displayLabel = getLastDepth(displayedHint); + let displayLabel = getLastDepth(displayedHint); + + if (type != 'js_method') { + const currentWordDepth = currentWord.split('.').length; + displayLabel = hint + .split('.') + .slice(currentWordDepth - 1) + .join('.'); + } return { - displayLabel: lastDepth === '' ? displayedHint : displayLabel, + displayLabel, label: displayedHint, info: displayedHint, type: type === 'js_method' ? 'js_methods' : type?.toLowerCase(), @@ -154,40 +163,24 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) => }; function filterHintsByDepth(input, hints) { - if (input === '') return hints; + const inputParts = input.split('.'); + const inputDepth = inputParts.length + 1; - const inputDepth = input.includes('.') ? input.split('.').length : 0; - - const filteredHints = hints.filter((cm) => { - const hintParts = cm.hint.split('.'); - - let shouldInclude = - (cm.hint.startsWith(input) && hintParts.length === inputDepth + 1) || - (cm.hint.startsWith(input) && hintParts.length === inputDepth); - - const shouldFuzzyMatch = !shouldInclude ? hintParts.length > inputDepth : false; - - if (shouldFuzzyMatch) { - // fuzzy match - let matchedDepth = -1; - for (let i = 0; i < hintParts.length; i++) { - if (hintParts[i].includes(input)) { - matchedDepth = i; - break; - } - } - - if (matchedDepth !== -1) { - shouldInclude = hintParts.length === matchedDepth + 1; - } - } else if (input.endsWith('.')) { - shouldInclude = cm.hint.startsWith(input) && hintParts.length === inputDepth; - } - - return shouldInclude; + const hintsWithDepth = hints.map((hint) => { + const hintParts = hint.hint.split('.'); + return { + ...hint, + depth: hintParts.length, + }; }); - return filteredHints; + const filteredHints = hintsWithDepth.filter((hint) => { + return hint.depth <= inputDepth; + }); + + const sortedHints = filteredHints.sort((hint1, hint2) => hint1.depth - hint2.depth); + + return sortedHints; } export function findNearestSubstring(inputStr, currentCurosorPos) { diff --git a/frontend/src/AppBuilder/QueryManager/Components/ChangeDataSource.jsx b/frontend/src/AppBuilder/QueryManager/Components/ChangeDataSource.jsx index 244668cf8a..85958cd97b 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/ChangeDataSource.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/ChangeDataSource.jsx @@ -1,8 +1,20 @@ -import React from 'react'; +import React, { useState } from 'react'; import Select from '@/_ui/Select'; import { decodeEntities } from '@/_helpers/utils'; +import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver'; export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleased }) => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + + usePopoverObserver( + document.getElementsByClassName('query-details')[0], + document.querySelector('.change-data-source-select.react-select__control'), + document.querySelector('.change-data-source-select.react-select__menu'), + isMenuOpen, + () => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'block'), + () => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'none') + ); + return ( + + +
+ +
+ +
+
+ + ); +}; + +export default BulkUpsertPrimaryKey; diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx index 89323c1c3c..e273e2ace8 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/RenderColumnUI.jsx @@ -1,5 +1,4 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; -import { resolveReferences } from '@/Editor/CodeEditor/utils'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import Trash from '@/_ui/Icon/solidIcons/Trash'; import React from 'react'; @@ -43,8 +42,7 @@ const RenderColumnUI = ({ placeholder="key" onChange={(newValue) => { if (isJSonTypeColumn) { - const [_, __, resolvedValue] = resolveReferences(`{{${newValue}}}`); - handleValueChange(resolvedValue); + handleValueChange(newValue); } else { handleValueChange(newValue); } diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx index 208f4df816..0ea9538017 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx @@ -220,7 +220,9 @@ function DataSourceSelect({ if (isFirstPageLoaded && offset >= totalRecords) return; if (foreignKeys.length < 1) return; setIsLoadingFKDetails(true); - const referencedColumns = foreignKeys.find((item) => item.column_names[0] === cellColumnName); + const referencedColumns = Array.isArray(foreignKeys) + ? foreignKeys.find((item) => item.column_names[0] === cellColumnName) + : undefined; if (!referencedColumns?.referenced_column_names?.length) return; const selectQuery = new PostgrestQueryBuilder(); @@ -709,7 +711,8 @@ const MenuList = ({ ...props }) => { const menuListStyles = getStyles('menuList', props); - const referencedColumnDetails = foreignKeys?.find((item) => item.column_names[0] === cellColumnName); + const referencedColumnDetails = + Array.isArray(foreignKeys) && foreignKeys?.find((item) => item?.column_names[0] === cellColumnName); const handleNavigateToReferencedTable = () => { const data = { diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx index e873228888..f25e72cb59 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations.jsx @@ -16,6 +16,7 @@ import { getPrivateRoute } from '@/_helpers/routes'; import { useNavigate } from 'react-router-dom'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { BulkUploadPrimaryKey } from './BulkUploadPrimaryKey'; +import BulkUpsertPrimaryKey from './BulkUpsertPrimaryKey'; import './styles.scss'; import CodeHinter from '@/AppBuilder/CodeEditor'; @@ -46,6 +47,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay const [tableForeignKeyInfo, setTableForeignKeyInfo] = useState({}); const [bulkUpdatePrimaryKey, setBulkUpdatePrimaryKey] = useState(() => options['bulk_update_with_primary_key'] || {}); + const [bulkUpsertPrimaryKey, setBulkUpsertPrimaryKey] = useState(() => options['bulk_upsert_with_primary_key'] || {}); const joinOptions = options['join_table']?.['joins'] || [ { conditions: { conditionsList: [{ leftField: { table: selectedTableId } }] } }, @@ -196,6 +198,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay // eslint-disable-next-line react-hooks/exhaustive-deps }, [bulkUpdatePrimaryKey]); + useEffect(() => { + mounted && optionchanged('bulk_upsert_with_primary_key', bulkUpsertPrimaryKey); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bulkUpsertPrimaryKey]); + useEffect(() => { mounted && optionchanged('update_rows', updateRowsOptions); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -234,10 +241,18 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay setBulkUpdatePrimaryKey((prev) => ({ ...prev, rows_update: value })); }; + const handleBulkUpsertRowsOptionChanged = (value) => { + setBulkUpsertPrimaryKey((prev) => ({ ...prev, rows: value })); + }; + const handlePrimaryKeyOptionChangedForBulkUpdate = (value) => { setBulkUpdatePrimaryKey((prev) => ({ ...prev, primary_key: value })); }; + const handlePrimaryKeyOptionChangedForBulkUpsert = (value) => { + setBulkUpsertPrimaryKey((prev) => ({ ...prev, primary_key: value })); + }; + const loadTableInformation = async (tableId, isNewTableAdded) => { const tableDetails = findTableDetails(tableId); if (tableDetails?.table_name && !tableInfo[tableDetails?.table_name]) { @@ -340,8 +355,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay tableForeignKeyInfo, setTableForeignKeyInfo, bulkUpdatePrimaryKey, + bulkUpsertPrimaryKey, handleBulkUpdateWithPrimaryKeysRowsUpdateOptionChanged, + handleBulkUpsertRowsOptionChanged, handlePrimaryKeyOptionChangedForBulkUpdate, + handlePrimaryKeyOptionChangedForBulkUpsert, }), [ organizationId, @@ -357,6 +375,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay joinOrderByOptions, selectedTableId, bulkUpdatePrimaryKey, + bulkUpsertPrimaryKey, ] ); @@ -517,6 +536,8 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay return JoinTable; case 'bulk_update_with_primary_key': return BulkUploadPrimaryKey; + case 'bulk_upsert_with_primary_key': + return BulkUpsertPrimaryKey; } }; @@ -527,6 +548,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay { label: 'Delete rows', value: 'delete_rows' }, { label: 'Join tables', value: 'join_tables' }, { label: 'Bulk update with primary key', value: 'bulk_update_with_primary_key' }, + { label: 'Bulk upsert with primary key', value: 'bulk_upsert_with_primary_key' }, ]; const ComponentToRender = getComponent(operation); diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx index 9aa77601e5..dcbf7b46ce 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx @@ -5,14 +5,25 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; import './workflows-query.scss'; import { v4 as uuidv4 } from 'uuid'; import useStore from '@/AppBuilder/_stores/store'; +import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver'; export function Workflows({ options, optionsChanged, currentState }) { const [workflowOptions, setWorkflowOptions] = useState([]); + const [isMenuOpen, setIsMenuOpen] = useState(false); const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined); const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]); const appId = useStore((state) => state.app.appId); + usePopoverObserver( + document.getElementsByClassName('query-details')[0], + document.querySelector('.workflow-select.react-select__control'), + document.querySelector('.workflow-select.react-select__menu'), + isMenuOpen, + () => (document.querySelector('.workflow-select.react-select__menu').style.display = 'block'), + () => (document.querySelector('.workflow-select.react-select__menu').style.display = 'none') + ); + useEffect(() => { appsService.getWorkflows(appId).then(({ workflows }) => { setWorkflowOptions( @@ -50,6 +61,13 @@ export function Workflows({ options, optionsChanged, currentState }) { customWrap={true} width="300px" menuPlacement="bottom" + customClassPrefix="workflow-select" + onMenuOpen={() => { + setIsMenuOpen(true); + }} + onMenuClose={() => { + setIsMenuOpen(false); + }} />
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx new file mode 100644 index 0000000000..314fe8ba89 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx @@ -0,0 +1,538 @@ +import React, { useState, useEffect } from 'react'; +import Accordion from '@/_ui/Accordion'; +import { EventManager } from '../EventManager'; +import { renderElement } from '../Utils'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import Popover from 'react-bootstrap/Popover'; +import List from '@/ToolJetUI/List/List'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; +import useStore from '@/AppBuilder/_stores/store'; +import CodeHinter from '@/AppBuilder/CodeEditor'; +import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton'; +import ListGroup from 'react-bootstrap/ListGroup'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import SortableList from '@/_components/SortableList'; +import Trash from '@/_ui/Icon/solidIcons/Trash'; +import { shallow } from 'zustand/shallow'; +import Switch from '@/Editor/CodeBuilder/Elements/Switch'; +import { usePrevious } from '@dnd-kit/utilities'; + +export function Steps({ componentMeta, darkMode, ...restProps }) { + const { + layoutPropertyChanged, + component, + dataQueries, + paramUpdated, + currentState, + eventsChanged, + apps, + allComponents, + pages, + } = restProps; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + + const isDynamicOptionsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value); + const variant = component?.component?.definition?.properties?.variant?.value; + const prevVariant = usePrevious(variant) + console.log("variant", component?.component?.definition); + + + const [options, setOptions] = useState([]); + const [hoveredOptionIndex, setHoveredOptionIndex] = useState(null); + let properties = []; + let additionalActions = []; + let optionsProperties = []; + + for (const [key] of Object.entries(componentMeta?.properties)) { + if (componentMeta?.properties[key]?.section === 'additionalActions') { + additionalActions.push(key); + } else if (componentMeta?.properties[key]?.accordian === 'Options') { + optionsProperties.push(key); + } else { + properties.push(key); + } + } + + // the default style of "number" & "titles" type are different for completed label + // TODO: Need to revisit this logic when text custom themes are implemented + useEffect(() => { + const completedLabelColor = component?.component?.definition?.styles?.completedLabel?.value; + if (variant !== prevVariant) { + if (variant === "numbers" && completedLabelColor === "#1B1F24") { + paramUpdated({ name: 'completedLabel' }, 'value', "#FFFFFF", 'styles', false, {}); + } else if (variant === "titles" && completedLabelColor === "#FFFFFF") { + paramUpdated({ name: 'completedLabel' }, 'value', "#1B1F24", 'styles', false, {}); + } + } + + }, [variant]) + + const getItemStyle = (isDragging, draggableStyle) => ({ + userSelect: 'none', + ...draggableStyle, + }); + + const updateAllOptionsParams = (options, props) => { + paramUpdated({ name: 'steps' }, 'value', options, 'properties', false, props); + }; + + const generateNewOptions = () => { + let found = false; + let label = ''; + let currentNumber = options.length + 1; + while (!found) { + label = `step ${currentNumber}`; + if (options.find((option) => option.name === label) === undefined) { + found = true; + } + currentNumber += 1; + } + return { + name: label, + id: currentNumber - 1, + tooltip: label, + visible: { value: '{{true}}' }, + disabled: { value: '{{false}}' }, + }; + }; + + const handleAddOption = () => { + let _option = generateNewOptions(); + const _items = [...options, _option]; + setOptions(_items); + updateAllOptionsParams(_items); + }; + const handleDeleteOption = (index) => { + const _items = options.filter((option, i) => i !== index); + setOptions(_items); + updateAllOptionsParams(_items, { isParamFromDropdownOptions: true }); + }; + + const handleLabelChange = (propertyName, value, index) => { + const _options = options.map((option, i) => { + if (i === index) { + return { + ...option, + [propertyName]: value, + }; + } + return option; + }); + setOptions(_options); + updateAllOptionsParams(_options); + }; + + const reorderOptions = async (startIndex, endIndex) => { + const result = [...options]; + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + setOptions(result); + updateAllOptionsParams(result); + }; + + const onDragEnd = ({ source, destination }) => { + if (!destination || source?.index === destination?.index) { + return; + } + reorderOptions(source.index, destination.index); + }; + + const handleOnFxPress = (active, index, key) => { + const _options = options.map((option, i) => { + if (i === index) { + return { + ...option, + [key]: { + ...option[key], + fxActive: active, + }, + }; + } + return option; + }); + setOptions(_options); + updateAllOptionsParams(_options); + }; + + const _renderOverlay = (item, index) => { + return ( + + +
+ + handleLabelChange('id', value, index)} + /> +
+
+ + handleLabelChange('name', value, index)} + /> +
+
+ + handleLabelChange('tooltip', value, index)} + /> +
+
+ + handleLabelChange( + 'visible', + { + value, + }, + index + ) + } + paramName={'visible'} + onFxPress={(active) => handleOnFxPress(active, index, 'visible')} + fxActive={item?.visible?.fxActive} + fieldMeta={{ + type: 'toggle', + displayName: 'Make editable', + }} + paramType={'toggle'} + /> +
+
+ handleLabelChange('disabled', { value }, index)} + onFxPress={(active) => handleOnFxPress(active, index, 'disabled')} + fxActive={item?.disabled?.fxActive} + fieldMeta={{ + type: 'toggle', + displayName: 'Make editable', + }} + paramType={'toggle'} + /> +
+
+
+ ); + }; + const _renderOptions = () => { + return ( + + { + onDragEnd(result); + }} + > + + {({ innerRef, droppableProps, placeholder }) => ( +
+ {options?.map((item, index) => { + return ( + + {(provided, snapshot) => ( +
+ +
+ setHoveredOptionIndex(index)} + onMouseLeave={() => setHoveredOptionIndex(null)} + {...restProps} + > +
+
+ +
+
+ {getResolvedValue(item.name)} +
+
+ {index === hoveredOptionIndex && ( + { + e.stopPropagation(); + handleDeleteOption(index); + }} + > + + + + + )} +
+
+
+
+
+
+ )} +
+ ); + })} + {placeholder} +
+ )} +
+
+ + Add new option + +
+ ); + }; + + const isDynamicStepsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value); + useEffect(() => { + setOptions(constructSteps()); + }, [component?.id, isDynamicStepsEnabled]); + + const constructSteps = () => { + try { + let optionsValue = isDynamicOptionsEnabled + ? component?.component?.definition?.properties?.schema?.value + : component?.component?.definition?.properties?.steps?.value; + let options = []; + + if (isDynamicOptionsEnabled || typeof optionsValue === 'string') { + options = getResolvedValue(optionsValue); + } else { + options = optionsValue?.map((option) => option); + } + return options.map((option) => { + const newOption = { ...option }; + + Object.keys(option).forEach((key) => { + if (typeof option[key]?.value === 'boolean') { + newOption[key]['value'] = `{{${option[key]?.value}}}`; + } + }); + + if (!('visible' in newOption)) { + newOption['visible'] = { value: '{{true}}' }; + } + return newOption; + }); + } catch (error) { + return []; + } + }; + + let items = []; + + items.push({ + title: 'Steps', + isOpen: true, + children: ( + <> + {properties + .filter((property) => !optionsProperties.includes(property)) + ?.map((property) => { + if (property === 'steps') { + return ( + <> + {renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + 'advanced', + 'properties', + currentState, + allComponents + )} + {isDynamicStepsEnabled + ? renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + 'schema', + 'properties', + currentState, + allComponents + ) + : _renderOptions()} + + ); + } + // else if (property === 'variant') { + // return renderTest( + // component, + // componentMeta, + // paramUpdated, + // dataQueries, + // 'variant', + // 'properties', + // currentState, + // allComponents, + // handleLabelChange + // ); + // } + return renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + property, + 'properties', + currentState, + allComponents, + darkMode + ); + })} + + ), + }); + + items.push({ + title: 'Events', + isOpen: true, + children: ( + + ), + }); + items.push({ + title: `Additional Actions`, + isOpen: true, + children: additionalActions.map((property) => { + return renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + property, + 'properties', + currentState, + allComponents, + darkMode, + componentMeta.properties?.[property]?.placeholder + ); + }), + }); + + items.push({ + title: 'Devices', + isOpen: true, + children: ( + <> + {renderElement( + component, + componentMeta, + layoutPropertyChanged, + dataQueries, + 'showOnDesktop', + 'others', + currentState, + allComponents + )} + {renderElement( + component, + componentMeta, + layoutPropertyChanged, + dataQueries, + 'showOnMobile', + 'others', + currentState, + allComponents + )} + + ), + }); + + return ; +} + +function renderTest(...props) { + const [ + component, + componentMeta, + paramUpdated, + dataQueries, + param, + paramType, + currentState, + components = {}, + darkMode = false, + placeholder = '', + validationFn, + ] = props; + const value = componentMeta?.definition?.properties?.variant?.value; + return ( +
+ { + paramUpdated({ name: 'variant' }, 'value', e, 'properties', false, props); + }} + meta={{ + ...componentMeta.properties[param], + fullWidth: true, + }} + paramName={param} + isIcon={false} + component={component.component.definition.name} + /> +
+ ); +} diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx index 0568b85ccd..135f2b450b 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx @@ -255,7 +255,7 @@ export const PropertiesTabElements = ({ paramType="properties" /> - {resolveReferences(column?.isEditable) && ( + {(column?.fxActiveFields?.includes('isEditable') || resolveReferences(column?.isEditable)) && ( { - return { name: action.name, value: action.id }; + let groupedOptions = ActionTypes.reduce((acc, action) => { + const groupName = action.group; + + if (!acc[groupName]) { + acc[groupName] = []; + } + + acc[groupName].push({ + label: action.name, + value: action.id, + }); + + return acc; + }, {}); + + let actionOptions = Object.keys(groupedOptions).map((groupName) => { + return { label: groupName, options: groupedOptions[groupName] }; }); let checkIfClicksAreInsideOf = document.querySelector('.cm-completionListIncompleteBottom'); @@ -124,6 +144,46 @@ export const EventManager = ({ }), }; + const actionStyles = { + ...styles, + menuList: (base) => ({ + ...base, + padding: '8px 0 8px 8px', + '&::-webkit-scrollbar': { + width: '10px', + }, + '&::-webkit-scrollbar-track': { + background: 'transparent', + }, + '&::-webkit-scrollbar-thumb': { + background: '#E4E7EB', + border: '1px solid transparent', + backgroundClip: 'content-box', + }, + '&::-webkit-scrollbar-thumb:hover': { + background: '#E4E7EB !important', + border: '1px solid transparent !important', + backgroundClip: 'content-box !important', + }, + '&:hover': { + '&::-webkit-scrollbar-thumb': { + background: '#E4E7EB !important', + border: '1px solid transparent !important', + backgroundClip: 'content-box !important', + }, + }, + }), + group: (base) => ({ + ...base, + padding: 0, + }), + groupHeading: (base) => ({ + ...base, + margin: 0, + padding: '0', + }), + }; + const actionLookup = Object.fromEntries(ActionTypes.map((actionType) => [actionType.id, actionType])); let alertTypes = [ @@ -394,6 +454,29 @@ export const EventManager = ({ return defaultValue; }; + const formatGroupLabel = (data) => { + if (data.label === 'run-action') return; + return ( +
+ ); + }; + + const CustomOption = (props) => { + return ( + +
+
+ {props.isSelected && } +
+ {props.label} +
+
+ ); + }; + function eventPopover(event, index) { return ( group.options) + .find((option) => option.value === event.actionId)} + components={{ Option: CustomOption }} search={false} onChange={(value) => handlerChanged(index, 'actionId', value)} placeholder={t('globals.select', 'Select') + '...'} - styles={styles} + styles={actionStyles} useMenuPortal={false} useCustomStyles={true} + formatGroupLabel={formatGroupLabel} /> @@ -1006,10 +1093,21 @@ export const EventManager = ({ placement={popoverPlacement || 'left'} rootClose={true} overlay={eventPopover(event.event, index)} - onHide={() => setFocusedEventIndex(null)} onToggle={(showing) => { + // If the toggle action should be skipped (e.g., due to a previous state change), reset the flag and exit early. + if (shouldSkipOnToggle.current) { + shouldSkipOnToggle.current = false; + return; + } + + // If there is already a focused event, set the skip flag to prevent unnecessary state updates. + if (focusedEventIndex !== null && showing) { + shouldSkipOnToggle.current = true; + } + if (showing) { setFocusedEventIndex(index); + lastFocusedEventIndex.current = index; } else { setFocusedEventIndex(null); } @@ -1018,6 +1116,7 @@ export const EventManager = ({ >
(document.getElementById('popover-basic').style.display = 'block'), + () => (document.getElementById('popover-basic').style.display = 'none') + ); + if (events.length === 0) { return ( <> diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx index e101069fdb..07b53c4454 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx @@ -36,6 +36,7 @@ import Inspect from '@/_ui/Icon/solidIcons/Inspect'; import classNames from 'classnames'; import { EMPTY_ARRAY } from '@/_stores/editorStore'; import { Select } from './Components/Select'; +import { Steps } from './Components/Steps.jsx'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; // import { componentTypes } from '@/Editor/WidgetManager/components'; @@ -90,6 +91,7 @@ const NEW_REVAMPED_COMPONENTS = [ 'VerticalDivider', 'ModalV2', 'Link', + 'Steps', ]; export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selectedComponentId }) => { @@ -539,8 +541,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte componentMeta.displayName === 'Toggle Switch (Legacy)' ? 'Toggle (Legacy)' : componentMeta.displayName === 'Toggle Switch' - ? 'Toggle Switch' - : componentMeta.component, + ? 'Toggle Switch' + : componentMeta.component, })} @@ -740,6 +742,8 @@ const GetAccordion = React.memo( case 'DatePickerV2': case 'TimePicker': return ; + case 'Steps': + return ; case 'PhoneInput': return ; case 'CurrencyInput': diff --git a/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx b/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx index 9ce3bbd4ce..5b0868c128 100644 --- a/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx +++ b/frontend/src/AppBuilder/RightSideBar/WidgetBox/WidgetBox.jsx @@ -14,6 +14,9 @@ const NEW_WIDGETS = [ 'TimePicker', 'ModalV2', 'TextArea', + 'EmailInput', + 'PhoneInput', + 'CurrencyInput', ]; export const WidgetBox = ({ component, darkMode }) => { diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index af820fca13..b546bc7205 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -168,6 +168,7 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod showViewerNavigation={!isPagesSidebarHidden} handleAppEnvironmentChanged={handleAppEnvironmentChanged} changeToDarkMode={changeToDarkMode} + switchPage={switchPage} /> )}
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/container.js b/frontend/src/AppBuilder/WidgetManager/widgets/container.js index 6dc9a679a4..04ddf805d9 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/container.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/container.js @@ -3,8 +3,8 @@ export const containerConfig = { displayName: 'Container', description: 'Group components', defaultSize: { - width: 10, - height: 200, + width: 13, + height: 480, }, component: 'Container', others: { diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/steps.js b/frontend/src/AppBuilder/WidgetManager/widgets/steps.js index 0a6a2cd575..9e71a89f90 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/steps.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/steps.js @@ -4,25 +4,38 @@ export const stepsConfig = { description: 'Step-by-step navigation aid', component: 'Steps', properties: { + variant: { + type: 'switch', + displayName: 'Variant', + validation: { schema: { type: 'string' }, defaultValue: 'titles' }, + options: [ + { displayName: 'Label', value: 'titles' }, + { displayName: 'Number', value: 'numbers' }, + { displayName: 'Plain', value: 'plain' }, + ], + accordian: 'label', + }, + schema: { + type: 'code', + displayName: 'Schema', + conditionallyRender: { + key: 'advanced', + value: true, + }, + accordian: 'Options', + }, steps: { type: 'code', - displayName: 'Steps', + displayName: '', + showLabel: false, validation: { schema: { type: 'array', - element: { type: 'object', object: { id: { type: 'number' } } }, + element: { type: 'object' }, }, defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`, }, }, - currentStep: { - type: 'code', - displayName: 'Current step', - validation: { - schema: { type: 'number' }, - defaultValue: 1, - }, - }, stepsSelectable: { type: 'toggle', displayName: 'Steps selectable', @@ -30,7 +43,38 @@ export const stepsConfig = { schema: { type: 'boolean' }, defaultValue: false, }, + section: 'additionalActions', }, + disabledState: { + type: 'toggle', + displayName: 'Disable', + validation: { schema: { type: 'boolean' } }, + section: 'additionalActions', + }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', + }, + advanced: { + type: 'toggle', + displayName: 'Dynamic options', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + accordian: 'Options', + }, + currentStep: { + type: 'code', + displayName: 'Current step', + validation: { + schema: { type: 'number' }, + defaultValue: 1, + }, + }, + }, defaultSize: { width: 22, @@ -40,46 +84,126 @@ export const stepsConfig = { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, + actions: [ + { + handle: 'setStep', + displayName: 'Set step', + params: [ + { + handle: 'option', + displayName: 'Option', + }, + ], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [{ handle: 'visible', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }], + }, + { + handle: 'setDisabled', + displayName: 'Set disabled', + params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{true}}', type: 'toggle' }], + }, + { + handle: 'resetSteps', + displayName: 'Reset steps', + params: [], + }, + { + handle: 'setStepVisible', + displayName: 'Set step visible', + params: [ + { + handle: 'id', + displayName: 'Step id', + }, + { + handle: 'visibility', + displayName: 'visibility', + defaultValue: '{{false}}', + type: 'toggle', + }, + ], + }, + { + handle: 'setStepDisable', + displayName: 'Set step disable', + params: [ + { + handle: 'id', + displayName: 'Step id', + }, + { + handle: 'disabled', + displayName: 'disabled', + defaultValue: '{{true}}', + type: 'toggle', + }, + ], + }, + ], events: { onSelect: { displayName: 'On select' }, }, styles: { - color: { + incompletedAccent: { type: 'colorSwatches', - displayName: 'colorSwatches', + displayName: 'Incompleted accent', + validation: { + schema: { type: 'string' }, + defaultValue: '#CCD1D5', + }, + accordian: 'steps', + }, + incompletedLabel: { + type: 'colorSwatches', + displayName: 'Incompleted label', + validation: { + schema: { type: 'string' }, + defaultValue: '#1B1F24', + }, + accordian: 'steps', + }, + completedAccent: { + type: 'colorSwatches', + displayName: 'Completed accent', validation: { schema: { type: 'string' }, defaultValue: 'var(--primary-brand)', }, + accordian: 'steps', }, - textColor: { + completedLabel: { type: 'colorSwatches', - displayName: 'Text color', + displayName: 'Completed label', validation: { schema: { type: 'string' }, - defaultValue: '#000000', + defaultValue: '#1B1F24', }, + accordian: 'steps', }, - theme: { - type: 'select', - displayName: 'Theme', + currentStepLabel: { + type: 'colorSwatches', + displayName: 'Current step label', + validation: { + schema: { type: 'string' }, + defaultValue: '#1B1F24', + }, + accordian: 'steps', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, options: [ - { name: 'titles', value: 'titles' }, - { name: 'numbers', value: 'numbers' }, - { name: 'plain', value: 'plain' }, + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, ], - validation: { - schema: { type: 'string' }, - defaultValue: 'titles', - }, - }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, + accordian: 'container', }, }, exposedVariables: { @@ -92,17 +216,35 @@ export const stepsConfig = { }, properties: { steps: { - value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`, + value: [ + { name: 'step 1', tooltip: '', id: 1, visible: { value: true }, disabled: { value: false } }, + { name: 'step 2', tooltip: '', id: 2, visible: { value: true }, disabled: { value: false } }, + { name: 'step 3', tooltip: '', id: 3, visible: { value: true }, disabled: { value: false } }, + { name: 'step 4', tooltip: '', id: 4, visible: { value: true }, disabled: { value: false } }, + { name: 'step 5', tooltip: '', id: 5, visible: { value: true }, disabled: { value: false } }, + ], }, + schema: { + value: `{{ [{ name: 'step 1', tooltip: '', id: 1,visible: true, disabled: false},{ name: 'step 2', tooltip: '', id: 2,visible: true, disabled: false},{ name: 'step 3', tooltip: '', id: 3,visible: true, disabled: false},{ name: 'step 4', tooltip: '', id: 4,visible: true, disabled: false},{ name: 'step 5', tooltip: '', id: 5,visible: true, disabled: false}]}}`, + }, + disabledState: { value: '{{false}}' }, + variant: { value: 'titles' }, currentStep: { value: '{{3}}' }, stepsSelectable: { value: true }, + advanced: { value: `{{false}}` }, + visibility: { value: '{{true}}' }, }, events: [], styles: { visibility: { value: '{{true}}' }, - theme: { value: 'titles' }, - color: { value: 'var(--primary-brand)' }, - textColor: { value: '' }, + // color: { value: '' }, + // textColor: { value: '' }, + padding: { value: 'default' }, + incompletedAccent: { value: '#E4E7EB' }, + incompletedLabel: { value: '#1B1F24' }, + completedAccent: { value: 'var(--primary-brand)' }, + completedLabel: { value: '#1B1F24' }, + currentStepLabel: { value: '#1B1F24' }, }, }, }; diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index f131b4eb59..433a677dc7 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -216,248 +216,237 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v } // const appDataPromise = appService.fetchApp(appId); - appDataPromise - .then(async (result) => { - let appData = { ...result }; - let editorEnvironment = result.editorEnvironment; - if (isPreviewForVersion) { - const rawDataQueries = appData?.data_queries; - const rawEditingVersionDataQueries = appData?.editing_version?.data_queries; - appData = convertAllKeysToSnakeCase(appData); + appDataPromise.then(async (result) => { + let appData = { ...result }; + let editorEnvironment = result.editorEnvironment; + if (isPreviewForVersion) { + const rawDataQueries = appData?.data_queries; + const rawEditingVersionDataQueries = appData?.editing_version?.data_queries; + appData = convertAllKeysToSnakeCase(appData); - appData.data_queries = rawDataQueries; - if (appData.editing_version && rawEditingVersionDataQueries) { - appData.editing_version.data_queries = rawEditingVersionDataQueries; - } + appData.data_queries = rawDataQueries; + if (appData.editing_version && rawEditingVersionDataQueries) { + appData.editing_version.data_queries = rawEditingVersionDataQueries; + } + editorEnvironment = { + id: environmentId, + name: queryParams.env, + }; + } + + let constantsResp; + if (mode !== 'edit') { + try { + const queryParams = { slug: slug }; + const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams); editorEnvironment = { - id: environmentId, - name: queryParams.env, + id: viewerEnvironment?.environment?.id, + name: viewerEnvironment?.environment?.name, }; + constantsResp = + isPublicAccess && appData.is_public + ? await orgEnvironmentConstantService.getConstantsFromPublicApp(slug, viewerEnvironment?.environment?.id) + : await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id); + } catch (error) { + console.error('Error fetching viewer environment:', error); } + } - let constantsResp; - if (mode !== 'edit') { - try { - const queryParams = { slug: slug }; - const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams); - editorEnvironment = { - id: viewerEnvironment?.environment?.id, - name: viewerEnvironment?.environment?.name, - }; - constantsResp = - isPublicAccess && appData.is_public - ? await orgEnvironmentConstantService.getConstantsFromPublicApp( - slug, - viewerEnvironment?.environment?.id - ) - : await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id); - } catch (error) { - console.error('Error fetching viewer environment:', error); - } - } + if (mode === 'edit') { + constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id); + } + // get the constants for specific environment + constantsResp.constants = extractEnvironmentConstantsFromConstantsList( + constantsResp?.constants, + editorEnvironment?.name + ); - if (mode === 'edit') { - constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id); - } - // get the constants for specific environment - constantsResp.constants = extractEnvironmentConstantsFromConstantsList( - constantsResp?.constants, - editorEnvironment?.name - ); + setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public); - setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public); + fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public); - fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public); - - const pages = appData.pages.map((page) => { - return page; - }); - const conversation = appData.ai_conversation; - const docsConversation = appData.ai_conversation_learn; - if (setConversation && setDocsConversation) { - setConversation(conversation); - setDocsConversation(docsConversation); - // important to control ai inputs - getCreditBalance(); - } - - let showWalkthrough = true; - // if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message - - // handles the getappdataby slug api call. Gets the homePageId from the appData. - const homePageId = - appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id; - - setApp({ - appName: appData.name, - appId: appData.id, - slug: appData.slug, - currentAppEnvironmentId: editorEnvironment.id, - isMaintenanceOn: - 'is_maintenance_on' in result - ? result.is_maintenance_on - : 'isMaintenanceOn' in result - ? result.isMaintenanceOn - : false, - organizationId: appData.organizationId || appData.organization_id, - homePageId: homePageId, - isPublic: appData.is_public, - creationMode: appData.creation_mode, - }); - setIsEditorFreezed(appData.should_freeze_editor); - const global_settings = mapKeys( - appData.editing_version?.global_settings || appData.global_settings, - (value, key) => camelCase(key) - ); - if (!global_settings?.theme) { - global_settings.theme = baseTheme; - } - setGlobalSettings(global_settings); - setPages(pages, moduleId); - setPageSettings( - computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)) - ); - - // set starting page as homepage initially - let startingPage = appData.pages.find((page) => page.id === homePageId); - - //no access to homepage, set to the next available page - if (startingPage?.restricted) { - startingPage = appData.pages.find((page) => !page?.restricted); - } - - if (initialLoadRef.current) { - // if initial load, check if the path has a page handle and set that as the starting page - const initialLoadPath = location.pathname.split('/').pop(); - - const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup); - if (page) { - // if page is disabled, and not editing redirect to home page - const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled); - - if (shouldRedirect) { - const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle); - window.history.replaceState(null, null, newUrl); - - if (page?.restricted) { - toast.error('Access to this page is restricted. Contact admin to know more.', { - className: 'text-nowrap w-auto mw-100', - }); - } - } else { - startingPage = page; - } - } - - // navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`); - } - - // Add page id and handle to the state on initial load - const currentState = window.history.state || {}; - const pageInfo = { - id: startingPage.id, - handle: startingPage.handle, - }; - const newState = { ...currentState, ...pageInfo }; - window.history.replaceState(newState, '', window.location.href); - - setCurrentPageHandle(startingPage.handle); - updateFeatureAccess(); - setCurrentPageId(startingPage.id, moduleId); - setResolvedPageConstants({ - id: startingPage?.id, - handle: startingPage?.handle, - name: startingPage?.name, - }); - setComponentNameIdMapping(moduleId); - updateEventsField('events', appData.events); - setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); - setAppHomePageId(homePageId); - - const queryData = - isPublicAccess || (mode !== 'edit' && appData.is_public) - ? appData - : await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id); - const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries; - dataQueries.forEach((query) => normalizeQueryTransformationOptions(query)); - setQueries(dataQueries); - if (dataQueries?.length > 0) { - setSelectedQuery(dataQueries[0]?.id); - initialiseResolvedQuery(dataQueries.map((query) => query.id)); - } - const constants = constantsResp?.constants; - - if (constants) { - const orgConstants = {}; - const orgSecrets = {}; - constants.map((constant) => { - if (constant.type !== 'Secret') { - orgConstants[constant.name] = constant.value; - } else { - orgSecrets[constant.name] = constant.value; - } - }); - setResolvedConstants(orgConstants); - setSecrets(orgSecrets); - } - setQueryMapping(moduleId); - - setResolvedGlobals('environment', editorEnvironment); - setResolvedGlobals('mode', { value: mode }); - setResolvedGlobals('currentUser', { - ...user, - groups: currentSession?.groups, - role: currentSession?.role?.name, - ssoUserInfo: currentSession?.ssoUserInfo, - ...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata) - ? { metadata: currentSession?.currentUser?.metadata } - : {}), - }); - setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search)))); - initDependencyGraph(moduleId); - setCurrentMode(mode); // TODO: set mode based on the slug/appDef - if ( - state.ai && - state?.prompt && - initialLoadRef.current && - (conversation?.aiConversationMessages || []).length === 0 - ) { - setSelectedSidebarItem('tooljetai'); - toggleLeftSidebar('true'); - sendMessage(state.prompt); - setConversationZeroState(true); - showWalkthrough = false; - } - // fetchDataSources(appData.editing_version.id, editorEnvironment.id); - if (!isPublicAccess) { - const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env'); - useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams); - fetchGlobalDataSources( - appData.organization_id, - appData.editing_version?.id || appData.current_version_id, - editorEnvironment.id - ); - } - useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed - updateReleasedVersionId(appData.current_version_id); - - setEditorLoading(false); - initialLoadRef.current = false; - // only show if app is not created from prompt - if (showWalkthrough) initEditorWalkThrough(); - checkAndSetTrueBuildSuggestionsFlag(); - return () => { - document.title = retrieveWhiteLabelText(); - }; - }) - .catch((error) => { - if (isPublicAccess) { - if (mode !== 'edit') { - handleError('view', error); - } - } + const pages = appData.pages.map((page) => { + return page; }); + const conversation = appData.ai_conversation; + const docsConversation = appData.ai_conversation_learn; + if (setConversation && setDocsConversation) { + setConversation(conversation); + setDocsConversation(docsConversation); + // important to control ai inputs + getCreditBalance(); + } + + let showWalkthrough = true; + // if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message + + // handles the getappdataby slug api call. Gets the homePageId from the appData. + const homePageId = + appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id; + + setApp({ + appName: appData.name, + appId: appData.id, + slug: appData.slug, + currentAppEnvironmentId: editorEnvironment.id, + isMaintenanceOn: + 'is_maintenance_on' in result + ? result.is_maintenance_on + : 'isMaintenanceOn' in result + ? result.isMaintenanceOn + : false, + organizationId: appData.organizationId || appData.organization_id, + homePageId: homePageId, + isPublic: appData.is_public, + creationMode: appData.creation_mode, + }); + setIsEditorFreezed(appData.should_freeze_editor); + const global_settings = mapKeys( + appData.editing_version?.global_settings || appData.global_settings, + (value, key) => camelCase(key) + ); + if (!global_settings?.theme) { + global_settings.theme = baseTheme; + } + setGlobalSettings(global_settings); + setPages(pages, moduleId); + setPageSettings( + computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)) + ); + + // set starting page as homepage initially + let startingPage = appData.pages.find((page) => page.id === homePageId); + + //no access to homepage, set to the next available page + if (startingPage?.restricted) { + startingPage = appData.pages.find((page) => !page?.restricted); + } + + if (initialLoadRef.current) { + // if initial load, check if the path has a page handle and set that as the starting page + const initialLoadPath = location.pathname.split('/').pop(); + + const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup); + if (page) { + // if page is disabled, and not editing redirect to home page + const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled); + + if (shouldRedirect) { + const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle); + window.history.replaceState(null, null, newUrl); + + if (page?.restricted) { + toast.error('Access to this page is restricted. Contact admin to know more.', { + className: 'text-nowrap w-auto mw-100', + }); + } + } else { + startingPage = page; + } + } + + // navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`); + } + + // Add page id and handle to the state on initial load + const currentState = window.history.state || {}; + const pageInfo = { + id: startingPage.id, + handle: startingPage.handle, + }; + const newState = { ...currentState, ...pageInfo }; + window.history.replaceState(newState, '', window.location.href); + + setCurrentPageHandle(startingPage.handle); + updateFeatureAccess(); + setCurrentPageId(startingPage.id, moduleId); + setResolvedPageConstants({ + id: startingPage?.id, + handle: startingPage?.handle, + name: startingPage?.name, + }); + setComponentNameIdMapping(moduleId); + updateEventsField('events', appData.events); + setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); + setAppHomePageId(homePageId); + + const queryData = + isPublicAccess || (mode !== 'edit' && appData.is_public) + ? appData + : await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id); + const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries; + dataQueries.forEach((query) => normalizeQueryTransformationOptions(query)); + setQueries(dataQueries); + if (dataQueries?.length > 0) { + setSelectedQuery(dataQueries[0]?.id); + initialiseResolvedQuery(dataQueries.map((query) => query.id)); + } + const constants = constantsResp?.constants; + + if (constants) { + const orgConstants = {}; + const orgSecrets = {}; + constants.map((constant) => { + if (constant.type !== 'Secret') { + orgConstants[constant.name] = constant.value; + } else { + orgSecrets[constant.name] = constant.value; + } + }); + setResolvedConstants(orgConstants); + setSecrets(orgSecrets); + } + setQueryMapping(moduleId); + + setResolvedGlobals('environment', editorEnvironment); + setResolvedGlobals('mode', { value: mode }); + setResolvedGlobals('currentUser', { + ...user, + groups: currentSession?.groups, + role: currentSession?.role?.name, + ssoUserInfo: currentSession?.ssoUserInfo, + ...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata) + ? { metadata: currentSession?.currentUser?.metadata } + : {}), + }); + setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search)))); + initDependencyGraph(moduleId); + setCurrentMode(mode); // TODO: set mode based on the slug/appDef + if ( + state.ai && + state?.prompt && + initialLoadRef.current && + (conversation?.aiConversationMessages || []).length === 0 + ) { + setSelectedSidebarItem('tooljetai'); + toggleLeftSidebar('true'); + sendMessage(state.prompt); + setConversationZeroState(true); + showWalkthrough = false; + } + // fetchDataSources(appData.editing_version.id, editorEnvironment.id); + if (!isPublicAccess) { + const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env'); + useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams); + fetchGlobalDataSources( + appData.organization_id, + appData.editing_version?.id || appData.current_version_id, + editorEnvironment.id + ); + } + useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed + updateReleasedVersionId(appData.current_version_id); + + setEditorLoading(false); + initialLoadRef.current = false; + // only show if app is not created from prompt + if (showWalkthrough) initEditorWalkThrough(); + checkAndSetTrueBuildSuggestionsFlag(); + return () => { + document.title = retrieveWhiteLabelText(); + }; + }); }, [setApp, setEditorLoading, currentSession]); useEffect(() => { diff --git a/frontend/src/AppBuilder/_hooks/usePopoverObserver.js b/frontend/src/AppBuilder/_hooks/usePopoverObserver.js index a5c0cda1c4..f047475db9 100644 --- a/frontend/src/AppBuilder/_hooks/usePopoverObserver.js +++ b/frontend/src/AppBuilder/_hooks/usePopoverObserver.js @@ -4,9 +4,9 @@ function usePopoverObserver(containerRef, triggerRef, popoverRef, show, onShow, const prevShow = useRef(false); // Check if it is a ref or a DOM element - const container = containerRef?.current ? containerRef.current : containerRef; - const trigger = triggerRef?.current ? triggerRef.current : triggerRef; - const popover = popoverRef?.current ? popoverRef.current : popoverRef; + const container = containerRef?.current !== undefined ? containerRef.current : containerRef; + const trigger = triggerRef?.current !== undefined ? triggerRef.current : triggerRef; + const popover = popoverRef?.current !== undefined ? popoverRef.current : popoverRef; useEffect(() => { if (!container || !trigger) return; diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index 252e387d70..79acc2c461 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -258,7 +258,11 @@ export const createDataQuerySlice = (set, get) => ({ set((state) => { state.dataQuery.creatingQueryInProcessId = null; state.dataQuery.queries.modules[moduleId] = [ - { ...data, data_source_id: queryToClone.data_source_id }, + { + ...data, + data_source_id: queryToClone.data_source_id, + plugin: { iconFile: queryToClone.plugin?.iconFile, icon_file: queryToClone.plugin?.icon_file }, + }, ...state.dataQuery.queries.modules[moduleId], ]; }); diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 5ca9429693..ddb67a5445 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -425,6 +425,7 @@ export const createQueryPanelSlice = (set, get) => ({ isLoading: false, ...(query.kind === 'restapi' ? { + metadata: data.metadata, request: data.data.requestObject, response: data.data.responseObject, responseHeaders: data.data.responseHeaders, diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index b76c8b072d..a253346eec 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -122,6 +122,7 @@ export const createResolvedSlice = (set, get) => ({ 'setVariables' ); get().updateDependencyValues(`variables.${key}`); + get().checkAndSetTrueBuildSuggestionsFlag(); }, getVariable: (key, moduleId = 'canvas') => { @@ -165,6 +166,7 @@ export const createResolvedSlice = (set, get) => ({ 'setPageVariable' ); get().updateDependencyValues(`page.variables.${key}`); + get().checkAndSetTrueBuildSuggestionsFlag(); }, getPageVariable: (key, moduleId = 'canvas') => { diff --git a/frontend/src/Editor/ActionTypes.js b/frontend/src/Editor/ActionTypes.js index 0bad71b3ac..e3fb69f95d 100644 --- a/frontend/src/Editor/ActionTypes.js +++ b/frontend/src/Editor/ActionTypes.js @@ -1,62 +1,36 @@ export const ActionTypes = [ + { + name: 'Run query', + id: 'run-query', + options: [{ queryId: '' }], + group: 'run-action', + }, { name: 'Show Alert', id: 'show-alert', options: [{ name: 'message', type: 'text', default: 'Message !' }], + group: 'run-action', }, { - name: 'Logout', - id: 'logout', - }, - { - name: 'Run Query', - id: 'run-query', - options: [{ queryId: '' }], - }, - { - name: 'Open Webpage', - id: 'open-webpage', - options: [{ name: 'url', type: 'text', default: 'https://example.com' }], - }, - { - name: 'Go to app', - id: 'go-to-app', + name: 'Control component', + id: 'control-component', options: [ - { name: 'app', type: 'text', default: '' }, - { name: 'queryParams', type: 'code', default: '[]' }, + { name: 'component', type: 'text', default: '' }, + { name: 'action', type: 'text', default: '' }, ], + group: 'control-component', }, { - name: 'Show Modal', + name: 'Show modal', id: 'show-modal', options: [{ name: 'modal', type: 'text', default: '' }], + group: 'control-component', }, { - name: 'Close Modal', + name: 'Close modal', id: 'close-modal', options: [{ name: 'modal', type: 'text', default: '' }], - }, - { - name: 'Copy to clipboard', - id: 'copy-to-clipboard', - options: [{ name: 'copy-to-clipboard', type: 'text', default: '' }], - }, - { - name: 'Set local storage', - id: 'set-localstorage-value', - options: [ - { name: 'key', type: 'code', default: '' }, - { name: 'value', type: 'code', default: '' }, - ], - }, - { - name: 'Generate file', - id: 'generate-file', - options: [ - { name: 'fileType', type: 'text', default: '' }, - { name: 'fileName', type: 'text', default: '' }, - { name: 'data', type: 'code', default: '{{[]}}' }, - ], + group: 'control-component', }, { name: 'Set table page', @@ -69,28 +43,28 @@ export const ActionTypes = [ }, { name: 'pageIndex', type: 'text', default: '{{1}}' }, ], - }, - { - name: 'Set variable', - id: 'set-custom-variable', - options: [ - { name: 'key', type: 'code', default: '' }, - { name: 'value', type: 'code', default: '' }, - ], - }, - { - name: 'Unset all variables', - id: 'unset-all-custom-variables', - }, - { - name: 'Unset variable', - id: 'unset-custom-variable', - options: [{ name: 'key', type: 'code', default: '' }], + group: 'control-component', }, { name: 'Switch page', id: 'switch-page', options: [{ name: 'page', type: 'text', default: '' }], + group: 'navigation', + }, + { + name: 'Go to app', + id: 'go-to-app', + options: [ + { name: 'app', type: 'text', default: '' }, + { name: 'queryParams', type: 'code', default: '[]' }, + ], + group: 'navigation', + }, + { + name: 'Open webpage', + id: 'open-webpage', + options: [{ name: 'url', type: 'text', default: 'https://example.com' }], + group: 'navigation', }, { name: 'Set page variable', @@ -99,10 +73,7 @@ export const ActionTypes = [ { name: 'key', type: 'code', default: '' }, { name: 'value', type: 'code', default: '' }, ], - }, - { - name: 'Unset all page variables', - id: 'unset-all-page-variables', + group: 'variable', }, { name: 'Unset page variable', @@ -111,14 +82,61 @@ export const ActionTypes = [ { name: 'key', type: 'code', default: '' }, { name: 'value', type: 'code', default: '' }, ], + group: 'variable', }, - { - name: 'Control component', - id: 'control-component', + name: 'Unset all page variables', + id: 'unset-all-page-variables', + group: 'variable', + }, + { + name: 'Set variable', + id: 'set-custom-variable', options: [ - { name: 'component', type: 'text', default: '' }, - { name: 'action', type: 'text', default: '' }, + { name: 'key', type: 'code', default: '' }, + { name: 'value', type: 'code', default: '' }, ], + group: 'variable', + }, + { + name: 'Unset variable', + id: 'unset-custom-variable', + options: [{ name: 'key', type: 'code', default: '' }], + group: 'variable', + }, + { + name: 'Unset all variables', + id: 'unset-all-custom-variables', + group: 'variable', + }, + { + name: 'Logout', + id: 'logout', + group: 'other', + }, + { + name: 'Generate file', + id: 'generate-file', + options: [ + { name: 'fileType', type: 'text', default: '' }, + { name: 'fileName', type: 'text', default: '' }, + { name: 'data', type: 'code', default: '{{[]}}' }, + ], + group: 'other', + }, + { + name: 'Set local storage', + id: 'set-localstorage-value', + options: [ + { name: 'key', type: 'code', default: '' }, + { name: 'value', type: 'code', default: '' }, + ], + group: 'other', + }, + { + name: 'Copy to clipboard', + id: 'copy-to-clipboard', + options: [{ name: 'copy-to-clipboard', type: 'text', default: '' }], + group: 'other', }, ]; diff --git a/frontend/src/Editor/Components/DraftEditor.jsx b/frontend/src/Editor/Components/DraftEditor.jsx index b2c8b61994..6734f3c865 100644 --- a/frontend/src/Editor/Components/DraftEditor.jsx +++ b/frontend/src/Editor/Components/DraftEditor.jsx @@ -1,7 +1,8 @@ /* eslint-disable react/no-string-refs */ import React from 'react'; -import { Editor, EditorState, RichUtils, getDefaultKeyBinding, ContentState, convertFromHTML } from 'draft-js'; +import { Editor, EditorState, RichUtils, getDefaultKeyBinding } from 'draft-js'; import 'draft-js/dist/Draft.css'; +import { stateFromHTML } from 'draft-js-import-html'; import { stateToHTML } from 'draft-js-export-html'; import Loader from '@/ToolJetUI/Loader/Loader'; import DOMPurify from 'dompurify'; @@ -150,11 +151,8 @@ const InlineStyleControls = (props) => { class DraftEditor extends React.Component { constructor(props) { super(props); - const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue)); this.state = { - editorState: EditorState.createWithContent( - ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap) - ), + editorState: EditorState.createWithContent(stateFromHTML(DOMPurify.sanitize(this.props.defaultValue))), }; this.editorContainerRef = React.createRef(); @@ -173,6 +171,18 @@ class DraftEditor extends React.Component { this.toggleInlineStyle = this._toggleInlineStyle.bind(this); } + componentDidUpdate(prevProps) { + if (prevProps.defaultValue !== this.props.defaultValue) { + const newContentState = stateFromHTML(DOMPurify.sanitize(this.props.defaultValue)); + const newEditorState = EditorState.createWithContent(newContentState); + const html = stateToHTML(newContentState); + + this.props.handleChange(html); + + this.setState({ editorState: newEditorState }); + } + } + componentDidMount() { //For resizing the editor container based on the height of rich text editor controls this.resizeObserver = new ResizeObserver(() => { @@ -193,11 +203,7 @@ class DraftEditor extends React.Component { isVisible: this.props.isVisible, isLoading: this.props.isLoading, setValue: async (text) => { - const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(text)); - const newContentState = ContentState.createFromBlockArray( - blocksFromHTML.contentBlocks, - blocksFromHTML.entityMap - ); + const newContentState = stateFromHTML(DOMPurify.sanitize(text)); const newEditorState = EditorState.createWithContent(newContentState); const html = stateToHTML(newContentState); this.props.handleChange(html); @@ -226,19 +232,6 @@ class DraftEditor extends React.Component { } } - componentDidUpdate(prevProps) { - if (prevProps.defaultValue !== this.props.defaultValue) { - const blocksFromHTML = convertFromHTML(DOMPurify.sanitize(this.props.defaultValue)); - const newContentState = ContentState.createFromBlockArray(blocksFromHTML.contentBlocks, blocksFromHTML.entityMap); - const newEditorState = EditorState.createWithContent(newContentState); - const html = stateToHTML(newContentState); - - this.props.handleChange(html); - - this.setState({ editorState: newEditorState }); - } - } - _handleKeyCommand(command, editorState) { const newState = RichUtils.handleKeyCommand(editorState, command); if (newState) { diff --git a/frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx b/frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx index f1a5d171b4..866565252e 100644 --- a/frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx +++ b/frontend/src/Editor/Components/RadioButtonV2/RadioButtonV2.jsx @@ -191,7 +191,7 @@ export const RadioButtonV2 = ({ data-cy={`label-${String(componentName).toLowerCase()} `} data-disabled={isDisabled} id={String(componentName)} - className={cx('radio-button,', 'd-flex', { + className={cx('radio-button', 'd-flex', { [alignment === 'top' && ((labelWidth != 0 && label?.length != 0) || (labelAutoWidth && labelWidth == 0 && label && label?.length != 0)) @@ -279,7 +279,7 @@ export const RadioButtonV2 = ({
step.id == activeStepId); + + useEffect(() => { + const sanitizedSteps = JSON.parse(JSON.stringify(steps || [])).map((step) => ({ + ...step, + visible: 'visible' in step ? step.visible : true, + disabled: 'disabled' in step ? step.disabled : false, + })); + const newFilteredSteps = (sanitizedSteps || []).filter((step) => step.visible); + setFilteredSteps(newFilteredSteps); + setStepsArr(sanitizedSteps); + }, [JSON.stringify(steps)]); + + // Common function to calculate progress bar width and label padding + const calculateProgressBarWidth = () => { + if (!containerRef.current || theme !== 'titles') return; + + const containerWidth = containerRef.current.offsetWidth; + setContainerWidth(containerWidth); + + const stepWidth = 20; // width of dot + padding + const totalStepsWidth = filteredSteps.length * stepWidth; + const totalProgressBars = filteredSteps.length - 1; + + if (filteredSteps.length === 1) { + setProgressBarWidth(containerWidth); + setContainerPadding(0); // No padding needed for single step + return; + } + + // Calculate progress bar width + const progressBarWidth = (containerWidth - totalStepsWidth) / totalProgressBars; + setProgressBarWidth(Math.min(progressBarWidth, (containerWidth - totalStepsWidth) / filteredSteps.length)); + + // Calculate container padding + if (firstLabelRef.current && lastLabelRef.current) { + const labelWidth = (containerWidth - (filteredSteps.length - 1) - 4) / filteredSteps.length; + + const firstLabelWidth = firstLabelRef.current.offsetWidth; + const lastLabelWidth = lastLabelRef.current.offsetWidth; + const maxLabelWidth = Math.max(firstLabelWidth, lastLabelWidth); + + const calculatedPadding = (maxLabelWidth / 2) - 1; + setContainerPadding(Math.max(2, calculatedPadding)); // Ensure minimum padding of 2px + } + }; + + // Add resize observer to track container width and calculate progress bar width + useEffect(() => { + calculateProgressBarWidth(); + if (theme !== 'titles') return; + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + calculateProgressBarWidth(); + } + }); + + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => resizeObserver.disconnect(); + }, [theme, JSON.stringify(steps), filteredSteps]); + // Dynamic styles for theming const dynamicStyle = { '--bgColor': styles.color, '--textColor': textColor, - }; - const activeStepHandler = (id) => { - const active = steps.filter((item) => item.id == id); - setExposedVariable('currentStepId', active[0].id); - fireEvent('onSelect'); - setActiveStep(active[0].id); + '--completedAccent': completedAccent === '#4368E3' ? 'var(--primary-brand)' : completedAccent, + '--incompletedAccent': incompletedAccent === '#E4E7EB' ? 'var(--surfaces-surface-03)' : incompletedAccent, + '--incompletedLabel': incompletedLabel === '#1B1F24' ? 'var(--text-primary)' : incompletedLabel, + '--completedLabel': completedLabel === '#1B1F24' ? 'var(--text-primary)' : completedLabel, + '--currentStepLabel': currentStepLabel === '#1B1F24' ? 'var(--text-primary)' : currentStepLabel, }; + // Step click handler + const handleStepClick = (id) => { + const step = filteredSteps.find((item) => item.id == id); + if (step && !step.disabled && !isDisabled) { + setActiveStepId(step.id); + fireEvent('onSelect'); + } + }; + + // Expose variables and methods useEffect(() => { - setActiveStep(currentStep); - setExposedVariable('currentStepId', currentStep); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentStep]); + setExposedVariable('isVisible', isVisible); + setExposedVariable('isDisabled', isDisabled); + setExposedVariable('currentStepId', activeStepId); + setExposedVariable('steps', stepsArr); + + setExposedVariable('setStepVisible', (stepId, visibility) => { + setStepsArr((prev) => { + const updatedSteps = prev.map((item) => + item.id == stepId ? { ...item, visible: visibility } : item + ); + setExposedVariable('steps', updatedSteps); + return updatedSteps; + }); + }); + + setExposedVariable('setStepDisable', (stepId, disabled) => { + setStepsArr((prev) => { + const updatedSteps = prev.map((item) => + item.id == stepId ? { ...item, disabled: disabled } : item + ); + setExposedVariable('steps', updatedSteps); + return updatedSteps; + }); + }); + + setExposedVariable('resetSteps', () => { + setActiveStepId(stepsArr.filter((step) => step.visible)?.[0]?.id); + }); + + setExposedVariable('setStep', (stepId) => { + if (!disabledState) setActiveStepId(stepId); + }); + setExposedVariable('setVisibility', (visibility) => setIsVisible(visibility)); + setExposedVariable('setDisable', (disabled) => setIsDisabled(disabled)); + }, [isVisible, isDisabled, activeStepId, stepsArr, disabledState]); + + // Update state from props + useEffect(() => setIsVisible(visibility), [visibility]); + useEffect(() => setIsDisabled(disabledState), [disabledState]); + useEffect(() => setActiveStepId(currentStepId), [currentStepId]); + + if (!isVisible) return null; return ( - visibility && ( -
- {steps?.map((item) => ( - stepsSelectable && activeStepHandler(item.id)} - style={dynamicStyle} - > - {theme == 'titles' && item.name} - - ))} +
+
+ {filteredSteps.map((step, index) => { + const isStepDisabled = step.disabled; + const isCompleted = index < currentStepIndex; + const isActive = index === currentStepIndex; + const isUpcoming = index > currentStepIndex; + const isFirstStep = index === 0; + const isLastStep = index === filteredSteps.length - 1; + + return ( + {/* using index as key to avoid issues due to duplicate step ids */} + +
stepsSelectable && handleStepClick(step.id)} + className={`milestone ${theme === 'numbers' ? 'numbers' : ''} ${isDisabled || isStepDisabled ? 'disabled' : '' + } ${isCompleted ? 'completed' : isActive ? 'active' : 'incomplete'}`} + > + {theme === 'numbers' ? ( + index + 1 + ) : ( + <> +
+ {theme === 'titles' && ( +
+ {step.name} +
+ )} + + )} +
+ + + {index < filteredSteps.length - 1 && ( +
+ )} + + ); + })}
- ) +
); }; diff --git a/frontend/src/Editor/Components/Steps.scss b/frontend/src/Editor/Components/Steps.scss new file mode 100644 index 0000000000..c61f185a73 --- /dev/null +++ b/frontend/src/Editor/Components/Steps.scss @@ -0,0 +1,132 @@ +.steps-container { + display: flex; + flex-direction: column; + width: 100%; + opacity: 1; + + &.disabled { + opacity: 0.5; + } + + &.single-step { + align-items: center; + } + + .progress-line-container { + display: flex; + align-items: center; + gap: 2px; + + &.single-step { + width: auto; + } + } + + .milestone { + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: visible; + transition: all 0.3s ease; + cursor: pointer; + + &.numbers { + width: 24px; + height: 24px; + border-radius: 50%; + font-size: 14px; + font-weight: 500; + box-sizing: content-box; + + &.completed { + background-color: var(--completedAccent); + color: var(--completedLabel); + border: 2px solid var(--completedAccent); + } + + &.active { + color: var(--currentStepLabel); + border: 2px solid var(--completedAccent); + } + + &.incomplete { + background-color: var(--incompletedAccent); + color: var(--incompletedLabel); + border: 2px solid var(--incompletedAccent); + } + } + + &.disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + .dot { + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + transition: all 0.3s ease; + box-sizing: content-box; + + &.completed { + background-color: var(--completedAccent); + border: 2px solid var(--completedAccent); + } + + &.active { + background-color: white; + border: 2px solid var(--primary-brand); + } + + &.incomplete { + background-color: var(--incompletedAccent); + border: 2px solid var(--incompletedAccent); + } + } + + .label { + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 18px; + text-align: center; + margin-top: 2px; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: max-content; + + &.completed { + color: var(--completedLabel); + } + + &.active { + color: var(--completedLabel); + } + + &.incomplete { + color: var(--incompletedLabel); + } + } + + .step-connector { + flex-grow: 1; + height: 2px; + align-self: center; + transition: all 0.3s ease; + + &.completed { + background-color: var(--completedAccent); + } + + &.incomplete { + background-color: var(--incompletedAccent); + } + } +} \ No newline at end of file diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx index 92772ffea1..d480f2c698 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/SelectBox.jsx @@ -220,7 +220,9 @@ function DataSourceSelect({ if (isFirstPageLoaded && offset >= totalRecords) return; if (foreignKeys.length < 1) return; setIsLoadingFKDetails(true); - const referencedColumns = foreignKeys.find((item) => item.column_names[0] === cellColumnName); + const referencedColumns = Array.isArray(foreignKeys) + ? foreignKeys.find((item) => item.column_names[0] === cellColumnName) + : undefined; if (!referencedColumns?.referenced_column_names?.length) return; const selectQuery = new PostgrestQueryBuilder(); @@ -715,7 +717,8 @@ const MenuList = ({ ...props }) => { const menuListStyles = getStyles('menuList', props); - const referencedColumnDetails = foreignKeys?.find((item) => item.column_names[0] === cellColumnName); + const referencedColumnDetails = + Array.isArray(foreignKeys) && foreignKeys.find((item) => item?.column_names[0] === cellColumnName); const handleNavigateToReferencedTable = () => { const data = { diff --git a/frontend/src/Editor/WidgetManager/configs/container.js b/frontend/src/Editor/WidgetManager/configs/container.js index 6dc9a679a4..04ddf805d9 100644 --- a/frontend/src/Editor/WidgetManager/configs/container.js +++ b/frontend/src/Editor/WidgetManager/configs/container.js @@ -3,8 +3,8 @@ export const containerConfig = { displayName: 'Container', description: 'Group components', defaultSize: { - width: 10, - height: 200, + width: 13, + height: 480, }, component: 'Container', others: { diff --git a/frontend/src/Editor/WidgetManager/configs/index.js b/frontend/src/Editor/WidgetManager/configs/index.js index 93e45fd06c..b26b6c84a2 100644 --- a/frontend/src/Editor/WidgetManager/configs/index.js +++ b/frontend/src/Editor/WidgetManager/configs/index.js @@ -47,7 +47,7 @@ import { verticalDividerConfig } from './verticalDivider'; import { customComponentConfig } from './customComponent'; import { buttonGroupConfig } from './buttonGroup'; import { pdfConfig } from './pdf'; -import { stepsConfig } from './steps'; +// import { stepsConfig } from './steps'; import { kanbanConfig } from './kanban'; import { colorPickerConfig } from './colorPicker'; import { treeSelectConfig } from './treeSelect'; @@ -106,7 +106,7 @@ export { customComponentConfig, buttonGroupConfig, pdfConfig, - stepsConfig, + // stepsConfig, kanbanConfig, kanbanBoardConfig, //!Depreciated colorPickerConfig, diff --git a/frontend/src/Editor/WidgetManager/configs/steps.js b/frontend/src/Editor/WidgetManager/configs/steps.js deleted file mode 100644 index a39c634919..0000000000 --- a/frontend/src/Editor/WidgetManager/configs/steps.js +++ /dev/null @@ -1,108 +0,0 @@ -export const stepsConfig = { - name: 'Steps', - displayName: 'Steps', - description: 'Step-by-step navigation aid', - component: 'Steps', - properties: { - steps: { - type: 'code', - displayName: 'Steps', - validation: { - schema: { - type: 'array', - element: { type: 'object', object: { id: { type: 'number' } } }, - }, - defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`, - }, - }, - currentStep: { - type: 'code', - displayName: 'Current step', - validation: { - schema: { type: 'number' }, - defaultValue: 1, - }, - }, - stepsSelectable: { - type: 'toggle', - displayName: 'Steps selectable', - validation: { - schema: { type: 'boolean' }, - defaultValue: false, - }, - }, - }, - defaultSize: { - width: 22, - height: 38, - }, - others: { - showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, - showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, - }, - events: { - onSelect: { displayName: 'On select' }, - }, - styles: { - color: { - type: 'color', - displayName: 'Color', - validation: { - schema: { type: 'string' }, - defaultValue: '#000000', - }, - }, - textColor: { - type: 'color', - displayName: 'Text color', - validation: { - schema: { type: 'string' }, - defaultValue: '#000000', - }, - }, - theme: { - type: 'select', - displayName: 'Theme', - options: [ - { name: 'titles', value: 'titles' }, - { name: 'numbers', value: 'numbers' }, - { name: 'plain', value: 'plain' }, - ], - validation: { - schema: { type: 'string' }, - defaultValue: 'titles', - }, - }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, - }, - }, - exposedVariables: { - currentStepId: '3', - }, - definition: { - others: { - showOnDesktop: { value: '{{true}}' }, - showOnMobile: { value: '{{false}}' }, - }, - properties: { - steps: { - value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`, - }, - currentStep: { value: '{{3}}' }, - stepsSelectable: { value: true }, - }, - events: [], - styles: { - visibility: { value: '{{true}}' }, - theme: { value: 'titles' }, - color: { value: '' }, - textColor: { value: '' }, - }, - }, -}; diff --git a/frontend/src/MarketplacePage/InstalledPlugins.jsx b/frontend/src/MarketplacePage/InstalledPlugins.jsx index 260b16c189..d0da8512a5 100644 --- a/frontend/src/MarketplacePage/InstalledPlugins.jsx +++ b/frontend/src/MarketplacePage/InstalledPlugins.jsx @@ -68,7 +68,7 @@ export const InstalledPlugins = () => { })} {!fetching && installedPlugins?.length === 0 && (
-

No results found

+

No plugins added. Please add a plugin from the Marketplace.

)}
diff --git a/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx b/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx index 41cedf6bc4..d847f4238c 100644 --- a/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/ColumnForm.jsx @@ -342,8 +342,39 @@ const ColumnForm = ({
)}
-
- Default value +
+
+ Default value +
+ {foreignKeyDetails?.length > 0 && isForeignKey && ( + 0 && isForeignKey} + > +
+ Set default value to Null + +
+
+ )}
-
+
{isTimestamp ? ( ) : ( - - - No data found -
- } - loader={ - <> - - - - - } - isLoading={true} - value={foreignKeyDefaultValue} - foreignKeyAccessInRowForm={true} - disabled={dataType === 'serial'} - topPlaceHolder={dataType === 'serial' ? 'Auto-generated' : 'Enter a value'} - onChange={(value) => { - setForeignKeyDefaultValue(value); - setDefaultValue(value?.value); - }} - onAdd={true} - addBtnLabel={'Open referenced table'} - foreignKeys={foreignKeyDetails} - setReferencedColumnDetails={setReferencedColumnDetails} - scrollEventForColumnValues={true} - cellColumnName={columnName} - columnDataType={dataType?.value} - isCreateColumn={true} - /> + <> + + + No data found +
+ } + loader={ + <> + + + + + } + isLoading={true} + value={foreignKeyDefaultValue} + foreignKeyAccessInRowForm={true} + disabled={dataType === 'serial'} + topPlaceHolder={ + dataType === 'serial' + ? 'Auto-generated' + : foreignKeyDefaultValue?.value === null + ? 'Null' + : 'Enter a value' + } + onChange={(value) => { + setForeignKeyDefaultValue(value); + setDefaultValue(value?.value); + }} + onAdd={true} + addBtnLabel={'Open referenced table'} + foreignKeys={foreignKeyDetails} + setReferencedColumnDetails={setReferencedColumnDetails} + scrollEventForColumnValues={true} + cellColumnName={columnName} + columnDataType={dataType?.value} + isCreateColumn={true} + /> + {defaultValue === null &&

Null

} + )}
- {isNotNull === true && dataType?.value !== 'serial' && rows.length > 0 && defaultValue.length <= 0 ? ( + {isNotNull === true && dataType?.value !== 'serial' && defaultValue?.length <= 0 ? ( Default value is required to populate this field in existing rows as NOT NULL constraint is added @@ -546,6 +596,10 @@ const ColumnForm = ({ checked={isNotNull} onChange={(e) => { setIsNotNull(e.target.checked); + if (e.target.checked && defaultValue === null) { + setForeignKeyDefaultValue({ label: '', value: '' }); + setDefaultValue(''); + } }} disabled={dataType?.value === 'serial'} /> @@ -602,7 +656,7 @@ const ColumnForm = ({ shouldDisableCreateBtn={ isEmpty(columnName) || isEmpty(dataType) || - (isNotNull === true && rows.length > 0 && isEmpty(defaultValue) && dataType?.value !== 'serial') || + (dataType?.value !== 'serial' && isNotNull === true && isEmpty(defaultValue)) || disabledSaveButton } showToolTipForFkOnReadDocsSection={true} diff --git a/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx b/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx index 3943964a16..071faf5e3e 100644 --- a/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx @@ -31,6 +31,8 @@ import DateTimePicker from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/D import { getLocalTimeZone, timeZonesWithOffsets } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util'; import CodeHinter from '@/AppBuilder/CodeEditor'; import { resolveReferences } from '@/AppBuilder/CodeEditor/utils'; +import Switch from '@/AppBuilder/CodeBuilder/Elements/Switch'; +import PostgrestQueryBuilder from '@/_helpers/postgrestQueryBuilder'; const ColumnForm = ({ onClose, @@ -102,6 +104,68 @@ const ColumnForm = ({ const [foreignKeyDetails, setForeignKeyDetails] = useState([]); + // Add function to validate default value + const validateDefaultValue = async () => { + if (!isMatchingForeignKeyColumn(selectedColumn?.Header)) return; + + try { + const referencedColumns = foreignKeys.find((item) => item.column_names[0] === selectedColumn?.Header); + + if (!referencedColumns?.referenced_column_names?.length) { + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + return; + } + + const selectQuery = new PostgrestQueryBuilder(); + selectQuery.select(referencedColumns.referenced_column_names[0]); + selectQuery.eq(referencedColumns.referenced_column_names[0], defaultValue); + + const query = selectQuery.url.toString(); + + const { data = [], error } = await tooljetDatabaseService.findOne( + organizationId, + referencedColumns.referenced_table_id, + query + ); + + if (error) { + toast.error(error?.message ?? `Failed to validate default value`); + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + return; + } + + if (data.length === 0) { + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + } + } catch (error) { + console.error('Error validating default value:', error); + setForeignKeyDefaultValue({ + value: '', + label: '', + }); + setDefaultValue(''); + } + }; + + // Add useEffect to validate on mount + useEffect(() => { + if (isMatchingForeignKeyColumn(selectedColumn?.Header) && defaultValue) { + validateDefaultValue(); + } + }, []); + useEffect(() => { toast.dismiss(); setForeignKeyDetails( @@ -471,6 +535,11 @@ const ColumnForm = ({ setDisabledSaveButton(columnName === ''); }, [columnName]); + useEffect(() => { + const shouldDisableForNullValue = dataType?.value !== 'serial' && isNotNull === true && isEmpty(defaultValue); + setDisabledSaveButton(shouldDisableForNullValue); + }, [isNotNull, defaultValue, dataType]); + const handleInputError = (bool = false) => { setDisabledSaveButton(bool); }; @@ -621,16 +690,52 @@ const ColumnForm = ({
)}
-
- Default value +
+
+ Default value +
+ {isMatchingForeignKeyColumn(selectedColumn?.Header) && ( + +
+ Set default value to Null + +
+
+ )}
+ -
+
{isTimestamp ? ( ) : ( - - - No data found -
- } - loader={ - <> - - - - - } - isLoading={true} - value={foreignKeyDefaultValue} - foreignKeyAccessInRowForm={true} - disabled={ - selectedColumn?.dataType === 'serial' || selectedColumn.constraints_type.is_primary_key === true - } - topPlaceHolder={selectedColumn?.dataType === 'serial' ? 'Auto-generated' : 'Enter a value'} - onChange={(value) => { - setForeignKeyDefaultValue(value); - setDefaultValue(value?.value); - }} - onAdd={true} - addBtnLabel={'Open referenced table'} - foreignKeys={foreignKeys} - setReferencedColumnDetails={setReferencedColumnDetails} - scrollEventForColumnValues={true} - cellColumnName={selectedColumn?.Header} - columnDataType={dataType} - isEditColumn={true} - /> + <> + + + No data found +
+ } + loader={ + <> + + + + + } + isLoading={true} + value={foreignKeyDefaultValue} + foreignKeyAccessInRowForm={true} + disabled={ + selectedColumn?.dataType === 'serial' || selectedColumn.constraints_type.is_primary_key === true + } + topPlaceHolder={ + selectedColumn?.dataType === 'serial' + ? 'Auto-generated' + : foreignKeyDefaultValue?.value === null || defaultValue === null + ? 'Null' + : 'Enter a value' + } + onChange={(value) => { + setForeignKeyDefaultValue(value); + setDefaultValue(value?.value); + }} + onAdd={true} + addBtnLabel={'Open referenced table'} + foreignKeys={foreignKeys} + setReferencedColumnDetails={setReferencedColumnDetails} + scrollEventForColumnValues={true} + cellColumnName={selectedColumn?.Header} + columnDataType={dataType} + isEditColumn={true} + /> + {defaultValue === null &&

Null

} + )}
+ {isNotNull === true && dataType?.value !== 'serial' && defaultValue?.length <= 0 ? ( + + Default value is required to populate this field in existing rows as NOT NULL constraint is added + + ) : null} {isNotNull === true && selectedColumn?.dataType !== 'serial' && rows.length > 0 && @@ -866,6 +990,10 @@ const ColumnForm = ({ checked={isNotNull} onChange={(e) => { setIsNotNull(e.target.checked); + if (e.target.checked && defaultValue === null) { + setForeignKeyDefaultValue({ label: '', value: '' }); + setDefaultValue(''); + } }} disabled={selectedColumn?.dataType === 'serial' || selectedColumn?.constraints_type?.is_primary_key} /> diff --git a/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx b/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx index 5451cc353f..98ff603242 100644 --- a/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx @@ -177,7 +177,9 @@ const EditRowForm = ({ } function isMatchingForeignKeyColumnDetails(columnHeader) { - const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader); + const matchingColumn = Array.isArray(foreignKeys) + ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader) + : undefined; return matchingColumn; } diff --git a/frontend/src/TooljetDatabase/Forms/RowForm.jsx b/frontend/src/TooljetDatabase/Forms/RowForm.jsx index 8fcc9ef152..58dd3d1000 100644 --- a/frontend/src/TooljetDatabase/Forms/RowForm.jsx +++ b/frontend/src/TooljetDatabase/Forms/RowForm.jsx @@ -151,7 +151,9 @@ const RowForm = ({ } function isMatchingForeignKeyColumnDetails(columnHeader) { - const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader); + const matchingColumn = Array.isArray(foreignKeys) + ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader) + : undefined; return matchingColumn; } diff --git a/frontend/src/TooljetDatabase/Table/index.jsx b/frontend/src/TooljetDatabase/Table/index.jsx index 5d98ce70fe..3a1cafa460 100644 --- a/frontend/src/TooljetDatabase/Table/index.jsx +++ b/frontend/src/TooljetDatabase/Table/index.jsx @@ -958,7 +958,9 @@ const Table = ({ collapseSidebar }) => { } function isMatchingForeignKeyColumnDetails(columnHeader) { - const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader); + const matchingColumn = Array.isArray(foreignKeys) + ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader) + : undefined; return matchingColumn; } diff --git a/frontend/src/_components/DynamicForm.jsx b/frontend/src/_components/DynamicForm.jsx index 0f5db30e9b..a71974afc4 100644 --- a/frontend/src/_components/DynamicForm.jsx +++ b/frontend/src/_components/DynamicForm.jsx @@ -26,6 +26,7 @@ import { Constants } from '@/_helpers/utils'; import { OverlayTrigger, Tooltip } from 'react-bootstrap'; import Sharepoint from '@/_components/Sharepoint'; import AccordionForm from './AccordionForm'; +import { generateCypressDataCy } from '../modules/common/helpers/cypressHelpers'; const DynamicForm = ({ schema, @@ -534,7 +535,7 @@ const DynamicForm = ({ const labelElement = ( )} -
+
{flipComponentDropdown.helpText && ( diff --git a/frontend/src/_helpers/dataSourceSchemaManager.js b/frontend/src/_helpers/dataSourceSchemaManager.js index abf2f3c9bb..6ce4f6f9a7 100644 --- a/frontend/src/_helpers/dataSourceSchemaManager.js +++ b/frontend/src/_helpers/dataSourceSchemaManager.js @@ -1,3 +1,4 @@ +import { datasourceService } from '@/_services'; import Ajv2020 from 'ajv'; const ajvOptions = { @@ -18,13 +19,18 @@ export default class DataSourceSchemaManager { this.validate = this.ajv.compile(this.schema); } - validateData(options) { - const data = this._convertDataSourceOptionsToData(options); - const valid = this.validate(data); - if (!valid) { - return { valid: false, errors: this.validate.errors }; + async validateData(options) { + const decryptedOptions = await datasourceService.getDecryptedOptions(options); + const data = this._convertDataSourceOptionsToData(decryptedOptions); + try { + const valid = this.validate(data); + if (!valid) { + return { valid: false, errors: this.validate.errors }; + } + return { valid: true, errors: [] }; + } catch (error) { + console.log('Validtion error: ', error); } - return { valid: true, errors: [] }; } getDefaults(options = {}) { @@ -95,10 +101,6 @@ export default class DataSourceSchemaManager { result[key] = value; } - // Add a dummy value to pass validation for encrypted keys - if (this.getEncryptedProperties().includes(key)) { - result[key] = 'REDACTED'; - } return result; }, {}); } diff --git a/frontend/src/_services/datasource.service.js b/frontend/src/_services/datasource.service.js index ef1a878db7..9ea24e99d0 100644 --- a/frontend/src/_services/datasource.service.js +++ b/frontend/src/_services/datasource.service.js @@ -11,8 +11,19 @@ export const datasourceService = { save, fetchOauth2BaseUrl, testSampleDb, + getDecryptedOptions, }; +function getDecryptedOptions(options) { + const requestOptions = { + method: 'POST', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify(options), + }; + return fetch(`${config.apiUrl}/data-sources/decrypt`, requestOptions).then(handleResponse); +} + function getAll(appVersionId, environment_id, includeStaticSources = false) { const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; let searchParams = new URLSearchParams( diff --git a/frontend/src/_services/organization.service.js b/frontend/src/_services/organization.service.js index 2075e5d9de..481e8576c4 100644 --- a/frontend/src/_services/organization.service.js +++ b/frontend/src/_services/organization.service.js @@ -13,6 +13,7 @@ export const organizationService = { getWorkspacesLimit, checkWorkspaceUniqueness, updateOrganization, + setDefaultWorkspace, }; function getUsersByValue(searchInput) { @@ -100,3 +101,8 @@ function checkWorkspaceUniqueness(name, slug) { const query = queryString.stringify({ name, slug }); return fetch(`${config.apiUrl}/organizations/is-unique?${query}`, requestOptions).then(handleResponse); } + +function setDefaultWorkspace(workspaceId) { + const requestOptions = { method: 'PATCH', headers: authHeader(), credentials: 'include' }; + return fetch(`${config.apiUrl}/organizations/${workspaceId}/default`, requestOptions).then(handleResponse); +} diff --git a/frontend/src/_styles/tabler.scss b/frontend/src/_styles/tabler.scss index 086712dfa5..6b021cce0d 100644 --- a/frontend/src/_styles/tabler.scss +++ b/frontend/src/_styles/tabler.scss @@ -7675,29 +7675,33 @@ fieldset:disabled .btn { } .rounded { - border-radius: 4px ; + border-radius: 4px; } .rounded-0 { border-radius: 0 !important } -.rounded-top-left{ +.rounded-top-left { border-top-left-radius: 4px; } -.rounded-top-left-0{ +.rounded-top-left-0 { border-top-left-radius: 0 !important; } -.rounded-top-right-0{ + +.rounded-top-right-0 { border-top-right-radius: 0 !important; } -.rounded-bottom-left-0{ + +.rounded-bottom-left-0 { border-bottom-left-radius: 0 !important; } -.rounded-bottom-right-0{ + +.rounded-bottom-right-0 { border-bottom-right-radius: 0 !important; } + .rounded-1 { border-radius: 2px !important } @@ -17484,8 +17488,8 @@ a.step-item:hover { .step-item:not(:first-child):after { position: absolute; - left: -50%; - width: 100%; + left: calc(-50% + 8px); + width: calc(100% - 16px); content: ""; transform: translateY(-50%) } @@ -17498,13 +17502,25 @@ a.step-item:hover { box-sizing: content-box; display: block; content: ""; - border: 2px solid #fff; + border: 2px solid transparent; border-radius: 50%; transform: translateX(-50%) } -.step-item.active { - font-weight: 600 +.steps.steps-counter { + .step-item:not(:first-child):after { + left: calc(-50% + 16px) !important; + width: calc(100% - 32px) !important; + } +} +.steps-counter .step-item:before { + color:var(--completedLabel) !important; + } + .steps .step-item.active:before{ + color : var(--currentStepLabel) !important; + } +.step-item { + font-weight: 500; } .step-item.active:before { @@ -17521,7 +17537,7 @@ a.step-item:hover { } .step-item.active~.step-item:before { - color: #656d77 !important + color: var(--incompletedLabel) !important } .steps-counter { @@ -17549,7 +17565,8 @@ a.step-item:hover { .steps-counter .step-item:before { font-size: .75rem; line-height: 1.5rem; - content: counter(steps) + content: counter(steps); + font-weight: 500 !important; } .steps-counter .step-item.active~.step-item:before { @@ -19156,4 +19173,4 @@ img { background: #1f2936; border-color: #dadcde } -} +} \ No newline at end of file diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 1b0c48850c..da4447da2a 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -4760,15 +4760,18 @@ input[type="text"] { .folder-list { overflow-y: scroll; scrollbar-width: thin; - scrollbar-color: #888 transparent; + scrollbar-color: #888 transparent; + &:hover { &::-webkit-scrollbar { display: block; width: 5px; } + &::-webkit-scrollbar-thumb { background-color: #888; } + &::-webkit-scrollbar-track { background-color: transparent; } @@ -6518,6 +6521,7 @@ div#driver-page-overlay { // steps-widget a.step-item-disabled { text-decoration: none; + opacity: 0.5; } .steps { @@ -6527,34 +6531,45 @@ a.step-item-disabled { .step-item.active~.step-item:after, .step-item.active~.step-item:before { - background: #f3f5f5 !important; + background: var(--incompletedAccent) !important; } .step-item.active:before { - background: #ffffff !important; + background: transparent !important; } .steps .step-item.active:before { - border-color: #b4b2b2 !important; + border-color: var(--completedAccent) !important; } .steps-item { color: var(--textColor) !important; } + +.step-item { + &.completed-label { + color: var(--completedLabel) !important; + } + + &.incompleted-label { + color: var(--incompletedLabel) !important; + } + + &.active-label { + color: var(--currentStepLabel) !important; + } +} + .step-item:before { - background: var(--bgColor) !important; + background-color: var(--completedAccent) !important; // remaining code } .step-item:after { - background: var(--bgColor) !important; + background: var(--completedAccent) !important; } -.step-item.active~.step-item { - color: var(--textColor) !important; - ; -} .notification-center-badge { @@ -9872,25 +9887,30 @@ tbody { .workspace-settings-table-wrap { max-width: 880px; margin: 0 auto; - .tj-user-table-wrapper{ + + .tj-user-table-wrapper { padding-right: 4px; - } - &:hover{ - .tj-user-table-wrapper{ - padding-right: 0px; - } - ::-webkit-scrollbar{ - display: block; - width: 4px; - } - ::-webkit-scrollbar-track{ - background: var(--base); - } - ::-webkit-scrollbar-thumb{ - background: var(--slate7); - border-radius: 6px; - } - } + } + + &:hover { + .tj-user-table-wrapper { + padding-right: 0px; + } + + ::-webkit-scrollbar { + display: block; + width: 4px; + } + + ::-webkit-scrollbar-track { + background: var(--base); + } + + ::-webkit-scrollbar-thumb { + background: var(--slate7); + border-radius: 6px; + } + } } @@ -10029,92 +10049,6 @@ tbody { } } } - - .manage-ws-table-body { - width: 100%; - - .workspace-table-row { - border-bottom: 1px solid var(--slate5); - height: 64px; - width: 100%; - - .ws-name { - padding-left: 8px; - - - .current-workspace-tag { - font-weight: 500; - color: var(--indigo9); - font-size: 12px; - display: flex; - height: 21px; - width: 130px; - align-items: center; - margin-left: 20px; - padding: 4px 8px 5px 8px; - border: 1px solid var(--indigo7); - background-color: var(--indigo3); - border-radius: 100px; - } - } - - .open-button-cont { - width: 44px; - padding: 0px 8px 0px 8px; - - .workspace-open-btn { - width: 28px; - height: 28px; - background-color: var(--slate1); - border: 1px solid var(--slate7); - box-shadow: none; - - &:hover { - background-color: var(--slate4); - } - } - } - - .archive-btn-cont { - width: 103px; - padding-right: 8px; - - .workspace-archive-btn { - width: 95px; - height: 28px; - background-color: var(--slate1); - box-shadow: none; - border: 1px solid var(--tomato7); - color: var(--tomato9); - - &:hover { - background-color: var(--tomato3); - } - - &:disabled { - border: 1px solid var(--slate7); - } - } - - .workspace-active-btn { - width: 95px; - height: 28px; - - background-color: var(--slate1); - box-shadow: none; - border: 1px solid var(--slate7); - color: var(--slate12); - - &:hover { - background-color: var(--slate7); - } - } - - - } - - } - } } .manage-workspace-table-wrap.dark-mode { @@ -10781,6 +10715,10 @@ tbody { width: 660px; } +.org-users-page-animation-fade { + height: 100%; +} + .org-users-page { .nav-tabs .nav-link.active { background-color: transparent !important; @@ -10947,6 +10885,25 @@ tbody { } } +.default-group-wrap-small { + gap: 4px; + width: 121px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--indigo3); + border-radius: 6px; + padding: 5px 10px; + + p { + font-weight: 500 !important; + line-height: 18px !important; + color: var(--indigo9); + font-family: 'IBM Plex Sans', sans-serif; + } +} + .sso-icon-wrapper { display: flex; flex-direction: row; @@ -12142,8 +12099,10 @@ tbody { letter-spacing: -0.02em; } } + .sidebar-list-wrap.sidebar-list-wrap-with-banner.isAdmin { height: calc(100vh - 371px); + &.resource-limit-reached { height: calc(100vh - 371px); } @@ -12345,6 +12304,17 @@ tbody { } } + .design-component-inputs textarea { + + &.valid-textarea { + border: 1.5px solid #519b62 !important; + } + + &.invalid-textarea { + border: 1.5px solid #e26367 !important; + } + } + } .tj-app-input-wrapper { @@ -13141,6 +13111,10 @@ tbody { background-color: var(--slate3) !important; } + textarea:disabled { + background-color: var(--slate3) !important; + } + .react-select__control--is-disabled { background-color: var(--slate3) !important; } @@ -15872,6 +15846,7 @@ textarea.tj-text-input-widget{ .rest-api-options-codehinter { height: 100%; + .cm-content>.cm-line { // max-width: 357px !important; } @@ -16303,19 +16278,20 @@ fieldset:disabled { } .datepicker-validation-half { - flex:1 1 calc(50% - 8px); + flex: 1 1 calc(50% - 8px); } .date-validation-wrapper { .field { - height:24px; + height: 24px; } .code-flex-wrapper { - flex-wrap:wrap; + flex-wrap: wrap; } + margin-bottom: 3px; } @@ -16324,57 +16300,60 @@ fieldset:disabled { } - .react-datepicker__day--disabled { +.react-datepicker__day--disabled { + color: #ccc !important; +} + +.react-datepicker__time-list { + li.react-datepicker__time-list-item--disabled.react-datepicker__time-list-item { color: #ccc !important; } - - .react-datepicker__time-list{ - li.react-datepicker__time-list-item--disabled.react-datepicker__time-list-item { - color: #ccc !important; - } - } - - .inspector-validation-date-picker { - .react-datepicker-wrapper{ - input { - background-color: #fff; - } - input.dark-theme { - background-color: var(--slate3); - color: var(--slate12); - } +} +.inspector-validation-date-picker { + .react-datepicker-wrapper { + input { + background-color: #fff; } - + + input.dark-theme { + background-color: var(--slate3); + color: var(--slate12); + } + } +} -.datetimepicker-component, #component-portal, .custom-inspector-validation-time-picker { + +.datetimepicker-component, +#component-portal, +.custom-inspector-validation-time-picker { .datepicker-component { .react-datepicker { border-radius: 10px; box-shadow: 8px 8px 16px 0px #3032331A; - height:auto; + height: auto; } } - + .react-datepicker-time-component { border-radius: 10px; - width:auto; + width: auto; - .custom-time-input{ - border-left:none; - border-radius:10px; + .custom-time-input { + border-left: none; + border-radius: 10px; box-shadow: 8px 8px 16px 0px #3032331A; } .time-input-body { - padding-bottom:0px; + padding-bottom: 0px; } - + .time-col { height: 200px; } @@ -16383,28 +16362,32 @@ fieldset:disabled { border-radius: 10px; box-shadow: 8px 8px 16px 0px #3032331A; } - - .react-datepicker-time__input-container{ - border-radius:10px; + + .react-datepicker-time__input-container { + border-radius: 10px; } } - + .dark-theme { - .react-datepicker__year-text, .react-datepicker__month-text { + + .react-datepicker__year-text, + .react-datepicker__month-text { color: #fff; } - .react-datepicker__year-text:hover, .react-datepicker__month-text:hover { - background-color: #9ba1a6 ; + .react-datepicker__year-text:hover, + .react-datepicker__month-text:hover { + background-color: #9ba1a6; } } - .tj-datepicker-widget-year-selector:hover, .tj-datepicker-widget-month-selector:hover { - padding:1px 6px; + .tj-datepicker-widget-year-selector:hover, + .tj-datepicker-widget-month-selector:hover { + padding: 1px 6px; } - .react-datepicker{ + .react-datepicker { display: grid; grid-auto-flow: column; border-top-right-radius: 0rem; @@ -16417,48 +16400,49 @@ fieldset:disabled { justify-content: center; align-items: center; } + .react-datepicker__year-wrapper { - display:grid; - grid-template-columns:repeat(3, 1fr); + display: grid; + grid-template-columns: repeat(3, 1fr); max-width: unset; - gap:10px; + gap: 10px; } .react-datepicker { border-radius: 10px; } - .react-datepicker__header--custom{ + .react-datepicker__header--custom { height: 34px; margin-bottom: 14px; } - .react-datepicker__year--container{ - height:208px; + .react-datepicker__year--container { + height: 208px; width: 250px; box-shadow: 8px 8px 16px 0px #3032331A; border-radius: 10px; } .react-datepicker__year-text--selected { - background-color: #4368E3 !important; - height:24px; - width:61.33px; - border-radius: 8px; - color: #fff ; + background-color: #4368E3 !important; + height: 24px; + width: 61.33px; + border-radius: 8px; + color: #fff; } - .react-datepicker__year-text{ - font-family:'IBM Plex Sans' ; + .react-datepicker__year-text { + font-family: 'IBM Plex Sans'; font-size: 12px; line-height: 16px; text-align: center; font-weight: 400; - height:24px; - width:61.33px; + height: 24px; + width: 61.33px; justify-content: center; align-items: center; - display:flex; + display: flex; } } @@ -16473,42 +16457,42 @@ fieldset:disabled { } .react-datepicker__month-container { - height:208px; + height: 208px; width: 250px; box-shadow: 8px 8px 16px 0px #3032331A; border-radius: 10px; } .react-datepicker__monthPicker { - display:flex; + display: flex; flex-direction: column; - gap:10px; + gap: 10px; } .react-datepicker__month-text--selected { background-color: #4368E3 !important; - height:24px; - width:61.33px; + height: 24px; + width: 61.33px; border-radius: 8px; - color: #fff ; + color: #fff; } .react-datepicker__month-wrapper { - display:flex; - gap:24px; + display: flex; + gap: 24px; } .react-datepicker__month-text { - font-family:'IBM Plex Sans' ; + font-family: 'IBM Plex Sans'; font-size: 12px; line-height: 16px; text-align: center; font-weight: 400; - height:24px; - width:61.33px; + height: 24px; + width: 61.33px; justify-content: center; align-items: center; - display:flex; + display: flex; } } @@ -16518,7 +16502,7 @@ fieldset:disabled { .react-datepicker__month-container { width: 100%; - width:250px; + width: 250px; } .react-datepicker__input-time-container { @@ -16533,12 +16517,12 @@ fieldset:disabled { color: #ccc !important; pointer-events: none; } - + .react-datepicker-time__input { margin-left: 0px !important; .dark-time-input { - color:#f4f6fa !important; + color: #f4f6fa !important; background-color: var(--surfaces-surface-01) !important; } } @@ -16546,15 +16530,15 @@ fieldset:disabled { .react-datepicker-wrapper { width: 100%; } - + .react-datepicker-time__caption { - display:none; + display: none; } .custom-time-input { background-color: #fff; border-left: 1px solid #CCD1D5; - border-top-right-radius: 10px; + border-top-right-radius: 10px; border-bottom-right-radius: 10px; } @@ -16568,18 +16552,18 @@ fieldset:disabled { border-bottom: 1px solid #CCD1D5; font-weight: 500; font-family: 'IBM Plex Sans'; - color:#ACB2B9; + color: #ACB2B9; } - + .time-input-body { padding-bottom: 12px; } .time-col { margin-top: 5px; - overflow-y: auto; + overflow-y: auto; overflow-x: hidden; - scrollbar-width: none; + scrollbar-width: none; height: 265px; width: 62px; } @@ -16587,12 +16571,12 @@ fieldset:disabled { .selected-time { background-color: #4368E3 !important; border-radius: 6px; - color:#fff; + color: #fff; } .time-item { - width: 50px; - height:22px; + width: 50px; + height: 22px; display: flex; justify-content: center; align-items: center; @@ -16932,16 +16916,17 @@ section.ai-message-prompt-input-wrapper { .tj-inspector-timepicker.dark-theme { - .react-datepicker { - color:#f4f6fa !important; + .react-datepicker { + color: #f4f6fa !important; background-color: var(--surfaces-surface-01) !important; } - .react-datepicker, .react-datepicker__header { + .react-datepicker, + .react-datepicker__header { border: 1px solid var(--borders-default); background-color: #1f2936; - .react-datepicker-time__header{ + .react-datepicker-time__header { color: #fff !important; } @@ -16949,25 +16934,27 @@ section.ai-message-prompt-input-wrapper { } .tj-inspector-timepicker { - padding:0px !important; + padding: 0px !important; .react-datepicker__time-list { - scrollbar-width: none; + scrollbar-width: none; } .react-datepicker__triangle { - display:none; + display: none; } } -.custom-inspector-validation-date-picker, .custom-inspector-validation-time-picker { +.custom-inspector-validation-date-picker, +.custom-inspector-validation-time-picker { flex-basis: 100% !important; font-family: monospace; font-size: 12px; - height:32px; - + height: 32px; + .react-datepicker-wrapper { width: 100%; + input { width: 100%; border: 1px solid var(--slate7); @@ -16975,23 +16962,23 @@ section.ai-message-prompt-input-wrapper { background-color: var(--base); background-color: #fff; color: rgb(0, 92, 197); - height:32px; + height: 32px; } input.dark-theme { background-color: #272822; color: rgb(174, 129, 255); - + } } - + } .custom-inspector-validation-time-picker { .custom-time-input { - border-left:none; - border-radius:10px; + border-left: none; + border-radius: 10px; } .time-col { @@ -16999,19 +16986,21 @@ section.ai-message-prompt-input-wrapper { } .react-datepicker__input-time-container { - border-radius:10px; + border-radius: 10px; } - - + + } .custom-inspector-validation-time-picker-popper { - border-radius:10px; + border-radius: 10px; } -.input-date-display-format, .input-date-time-format { +.input-date-display-format, +.input-date-time-format { height: 60px; + .hide-fx { opacity: 0; transition: opacity 0.3s ease; @@ -17030,8 +17019,9 @@ section.ai-message-prompt-input-wrapper { color: white; } - .react-datepicker__day:hover, .react-datepicker__day--selecting-range-end { - background-color: var(--interactive-overlays-fill-hover) !important ; + .react-datepicker__day:hover, + .react-datepicker__day--selecting-range-end { + background-color: var(--interactive-overlays-fill-hover) !important; } .react-datepicker__day--keyboard-selected { @@ -17054,15 +17044,17 @@ section.ai-message-prompt-input-wrapper { .tj-daterange-widget { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; - .react-datepicker__day--in-selecting-range, .react-datepicker__day--in-range { - border-radius:0px; + .react-datepicker__day--in-selecting-range, + .react-datepicker__day--in-range { + border-radius: 0px; background-color: #4368E31A !important; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17073,44 +17065,48 @@ section.ai-message-prompt-input-wrapper { background-color: #ededee !important; } - .react-datepicker__day--selecting-range-start, .react-datepicker__day--selected, .react-datepicker__day--range-end { - border-radius:8px !important; + .react-datepicker__day--selecting-range-start, + .react-datepicker__day--selected, + .react-datepicker__day--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__day--in-range:has(+ .react-datepicker__day--range-end), .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--selecting-range-end) { + .react-datepicker__day--in-range:has(+ .react-datepicker__day--range-end), + .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__day--in-range:has(+ .react-datepicker__day--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__day--range-start + .react-datepicker__day--in-range, .react-datepicker__day--selecting-range-start + .react-datepicker__day--in-selecting-range{ + .react-datepicker__day--range-start+.react-datepicker__day--in-range, + .react-datepicker__day--selecting-range-start+.react-datepicker__day--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__day--range-start + .react-datepicker__day--in-range { + .react-datepicker__day--range-start+.react-datepicker__day--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - + .react-datepicker__week { - .react-datepicker__day--in-range:first-of-type, - .react-datepicker__day--in-selecting-range:first-of-type, - .react-datepicker__day--outside-month + .react-datepicker__day--in-range, - .react-datepicker__day--outside-month + .react-datepicker__day--in-selecting-range{ + .react-datepicker__day--in-range:first-of-type, + .react-datepicker__day--in-selecting-range:first-of-type, + .react-datepicker__day--outside-month+.react-datepicker__day--in-range, + .react-datepicker__day--outside-month+.react-datepicker__day--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__day--in-range:last-of-type, - .react-datepicker__day--in-selecting-range:last-of-type, - .react-datepicker__day--in-range:has(+ .react-datepicker__day--outside-month), - .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--outside-month){ + .react-datepicker__day--in-range:last-of-type, + .react-datepicker__day--in-selecting-range:last-of-type, + .react-datepicker__day--in-range:has(+ .react-datepicker__day--outside-month), + .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--outside-month) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } @@ -17128,8 +17124,8 @@ section.ai-message-prompt-input-wrapper { } .tj-datepicker-widget-right { - position: absolute; - right: 10px; + position: absolute; + right: 10px; } .tj-datepicker-widget-left { @@ -17152,41 +17148,42 @@ section.ai-message-prompt-input-wrapper { } .react-datepicker { - border-radius:10px !important; - border:none; + border-radius: 10px !important; + border: none; } - + } -.tj-daterangepicker-widget-month-selector, .tj-daterangepicker-widget-year-selector { - appearance: none; - -moz-appearance: none; - -webkit-appearance: none; - padding-right: 4px; - /* Add some padding on the right to create space for custom arrow */ - background-image: url('data:image/svg+xml;utf8,'); - /* Add a custom arrow (you can use your own SVG) */ - background-repeat: no-repeat; - background-position: right center; - border: none; - /* Remove the default border */ - padding: 8px; - /* Adjust padding as needed */ - cursor: pointer; - /* Add pointer cursor for better usability */ - background: none; - padding: 0px; - height: 24px; - text-align: center; - color: var(--text-primary); - font-weight: 500; - width:auto; +.tj-daterangepicker-widget-month-selector, +.tj-daterangepicker-widget-year-selector { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + padding-right: 4px; + /* Add some padding on the right to create space for custom arrow */ + background-image: url('data:image/svg+xml;utf8,'); + /* Add a custom arrow (you can use your own SVG) */ + background-repeat: no-repeat; + background-position: right center; + border: none; + /* Remove the default border */ + padding: 8px; + /* Adjust padding as needed */ + cursor: pointer; + /* Add pointer cursor for better usability */ + background: none; + padding: 0px; + height: 24px; + text-align: center; + color: var(--text-primary); + font-weight: 500; + width: auto; } .datepicker-widget { - .react-datepicker-wrapper{ - width:100% !important; + .react-datepicker-wrapper { + width: 100% !important; } } @@ -17200,26 +17197,29 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-month-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__month-container { box-shadow: none !important; } - + .react-datepicker__month-text { - height:26px !important; + height: 26px !important; margin: 0px; - width:100% !important; + width: 100% !important; } - .react-datepicker__month-text--in-selecting-range, .react-datepicker__month-text--in-range { - border-radius:0px; + .react-datepicker__month-text--in-selecting-range, + .react-datepicker__month-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17232,45 +17232,49 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__month-text--selecting-range-start, .react-datepicker__month-text--selected, .react-datepicker__month-text--range-end { - border-radius:8px !important; + .react-datepicker__month-text--selecting-range-start, + .react-datepicker__month-text--selected, + .react-datepicker__month-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range, .react-datepicker__month-text--selecting-range-start + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--selecting-range-start+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range { + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - .react-datepicker__month-wrapper{ - gap:0px !important; - .react-datepicker__month-text--in-range:first-of-type, - .react-datepicker__month-text--in-selecting-range:first-of-type, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-range, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-wrapper { + gap: 0px !important; + + .react-datepicker__month-text--in-range:first-of-type, + .react-datepicker__month-text--in-selecting-range:first-of-type, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--in-range:last-of-type, - .react-datepicker__month-text--in-selecting-range:last-of-type, - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), - .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text){ + .react-datepicker__month-text--in-range:last-of-type, + .react-datepicker__month-text--in-selecting-range:last-of-type, + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } @@ -17290,44 +17294,47 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-year-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__year-container { box-shadow: none !important; } - .react-datepicker__year-wrapper{ - gap:0px !important; + .react-datepicker__year-wrapper { + gap: 0px !important; - .react-datepicker__year-text--in-range:first-of-type, - .react-datepicker__year-text--in-selecting-range:first-of-type{ + .react-datepicker__year-text--in-range:first-of-type, + .react-datepicker__year-text--in-selecting-range:first-of-type { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--in-range:last-of-type, - .react-datepicker__year-text--in-selecting-range:last-of-type{ + .react-datepicker__year-text--in-range:last-of-type, + .react-datepicker__year-text--in-selecting-range:last-of-type { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } } - + .react-datepicker__year-text { - height:26px !important; + height: 26px !important; margin-top: 5px !important; - margin-bottom:5px !important; + margin-bottom: 5px !important; margin: 0px; - width:62px !important; + width: 62px !important; } - .react-datepicker__year-text--in-selecting-range, .react-datepicker__year-text--in-range { - border-radius:0px; + .react-datepicker__year-text--in-selecting-range, + .react-datepicker__year-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17340,31 +17347,35 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__year-text--selecting-range-start, .react-datepicker__year-text--selected, .react-datepicker__year-text--range-end { - border-radius:8px !important; + .react-datepicker__year-text--selecting-range-start, + .react-datepicker__year-text--selected, + .react-datepicker__year-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), + .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range, .react-datepicker__year-text--selecting-range-start + .react-datepicker__year-text--in-selecting-range{ + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range, + .react-datepicker__year-text--selecting-range-start+.react-datepicker__year-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range { + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - + + } .dark-theme { @@ -18393,6 +18404,10 @@ section.ai-message-prompt-input-wrapper { } } +.codehinter-copilot-btn { + z-index: 1000; +} + #copilot-menu { width: 440px; max-width: 440px; @@ -18854,6 +18869,7 @@ section.ai-message-prompt-input-wrapper { font-style: normal; font-weight: 400; line-height: 18px; + &.dark { background: #FFFAEB !important; } diff --git a/frontend/src/_ui/HttpHeaders/SourceEditor.jsx b/frontend/src/_ui/HttpHeaders/SourceEditor.jsx index 04df343740..dda0775468 100644 --- a/frontend/src/_ui/HttpHeaders/SourceEditor.jsx +++ b/frontend/src/_ui/HttpHeaders/SourceEditor.jsx @@ -21,8 +21,7 @@ export default ({ return (
{options.length === 0 && ( -
+
There are no key value pairs added
@@ -86,7 +85,7 @@ export default ({
addNewKeyValuePair(options)} diff --git a/frontend/src/_ui/HttpHeaders/index.js b/frontend/src/_ui/HttpHeaders/index.js index 04bbd8e3b5..3f254d1497 100644 --- a/frontend/src/_ui/HttpHeaders/index.js +++ b/frontend/src/_ui/HttpHeaders/index.js @@ -1,13 +1,14 @@ -import React from "react"; -import _ from "lodash"; -import QueryEditor from "./QueryEditor"; -import SourceEditor from "./SourceEditor"; -import { deepClone } from "@/_helpers/utilities/utils.helpers"; +import React from 'react'; +import _ from 'lodash'; +import QueryEditor from './QueryEditor'; +import SourceEditor from './SourceEditor'; +import { deepClone } from '@/_helpers/utilities/utils.helpers'; export default ({ getter, - options = [["", ""]], + options = [['', '']], optionchanged, + handleOptionChange, isRenderedAsQueryEditor, workspaceConstants, isDisabled, @@ -16,27 +17,46 @@ export default ({ dataCy, }) => { function addNewKeyValuePair(options) { - const newPairs = [...options, ["", ""]]; - optionchanged(getter, newPairs); + const newPairs = [...options, ['', '']]; + + if (handleOptionChange) { + handleOptionChange(getter, newPairs, true); + } else { + optionchanged(getter, newPairs); + } } function removeKeyValuePair(index) { const newOptions = [...options]; newOptions.splice(index, 1); - optionchanged(getter, newOptions); + if (handleOptionChange) { + handleOptionChange(getter, newOptions, true); + } else { + optionchanged(getter, newOptions); + } } function keyValuePairValueChanged(value, keyIndex, index) { if (!isRenderedAsQueryEditor) { const newOptions = deepClone(options); newOptions[index][keyIndex] = value; - options.length - 1 === index - ? addNewKeyValuePair(newOptions) - : optionchanged(getter, newOptions); + if (options.length - 1 === index) { + addNewKeyValuePair(newOptions); + } else { + if (handleOptionChange) { + handleOptionChange(getter, newOptions, true); + } else { + optionchanged(getter, newOptions); + } + } } else { let newOptions = deepClone(options); newOptions[index][keyIndex] = value; - optionchanged(getter, newOptions); + if (handleOptionChange) { + handleOptionChange(getter, newOptions, true); + } else { + optionchanged(getter, newOptions); + } } } @@ -53,10 +73,6 @@ export default ({ return isRenderedAsQueryEditor ? ( ) : ( - + ); }; diff --git a/frontend/src/_ui/Icon/solidIcons/Moon.jsx b/frontend/src/_ui/Icon/solidIcons/Moon.jsx new file mode 100644 index 0000000000..913a345c56 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Moon.jsx @@ -0,0 +1,23 @@ +import React from 'react'; + +const Moon = ({ fill = '#6A727C', width = '24', className = '', viewBox = '0 0 24 24' }) => { + return ( + + + + ); +}; + +export default Moon; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index a190dd4c52..11ab593839 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -235,6 +235,7 @@ import Code from './Code.jsx'; import WorkflowV3 from './WorkflowV3.jsx'; import WorkspaceV3 from './WorkspaceV3.jsx'; import EnterpriseCrown from './EnterrpiseCrown.jsx'; +import Moon from './Moon.jsx'; const Icon = (props) => { switch (props.name) { @@ -710,6 +711,8 @@ const Icon = (props) => { return ; case 'play01': return ; + case 'moon': + return ; default: return ; } diff --git a/frontend/src/_ui/Input-V3/index.js b/frontend/src/_ui/Input-V3/index.js index abc819c80a..71b2b3cf2a 100644 --- a/frontend/src/_ui/Input-V3/index.js +++ b/frontend/src/_ui/Input-V3/index.js @@ -43,7 +43,7 @@ const InputV3 = ({ helpText, ...props }) => { required={props.isRequired} /> )} - {(widget === 'password-v3' || encrypted) && ( + {(widget === 'password-v3' || widget === 'password-v3-textarea' || encrypted) && (
{ label={props.label} placeholder={props.placeholder} required={props.isRequired} + multiline={widget === 'password-v3-textarea'} />
)} diff --git a/frontend/src/_ui/Select/SelectComponent.jsx b/frontend/src/_ui/Select/SelectComponent.jsx index 2d62352000..5c86b0b405 100644 --- a/frontend/src/_ui/Select/SelectComponent.jsx +++ b/frontend/src/_ui/Select/SelectComponent.jsx @@ -24,6 +24,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele isDisabled = false, borderRadius, openMenuOnFocus = false, + customClassPrefix = '', } = restProps; const customStyles = useCustomStyles ? styles : defaultStyles(isDarkMode, width, height, styles, borderRadius); @@ -74,7 +75,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele maxMenuHeight={maxMenuHeight} menuPortalTarget={useMenuPortal ? document.body : menuPortalTarget} closeMenuOnSelect={closeMenuOnSelect ?? true} - classNamePrefix={`${isDarkMode && 'dark-theme'} ${'react-select'}`} + classNamePrefix={`${customClassPrefix} ${isDarkMode && 'dark-theme'} ${'react-select'}`} /> ); }; diff --git a/frontend/src/components/ui/Input/CommonInput/Index.jsx b/frontend/src/components/ui/Input/CommonInput/Index.jsx index 0710876ac3..2b968a0940 100644 --- a/frontend/src/components/ui/Input/CommonInput/Index.jsx +++ b/frontend/src/components/ui/Input/CommonInput/Index.jsx @@ -3,6 +3,7 @@ import NumberInput from './NumberInput'; import TextInput from './TextInput'; import { HelperMessage, InputLabel, ValidationMessage } from '../InputUtils/InputUtils'; import { ButtonSolid } from '../../../../_components/AppButton'; +import { generateCypressDataCy } from '../../../../modules/common/helpers/cypressHelpers.js'; const CommonInput = ({ label, helperText, disabled, required, onChange: change, ...restProps }) => { const { type, encrypted, validation, isValidatedMessages, isDisabled } = restProps; @@ -32,21 +33,28 @@ const CommonInput = ({ label, helperText, disabled, required, onChange: change, } }, [isValidatedMessages]); + useEffect(() => { + if (isValid === true && (!isValidatedMessages || isValidatedMessages.valid === null)) { + setIsValid(true); + } + }, [isValid, isValidatedMessages]); + const toggleEditing = () => { if (isDisabled) return; const willBeInEditMode = !isEditing; setIsEditing(willBeInEditMode); - - if (willBeInEditMode) { - change({ target: { value: '' } }); - } + change({ target: { value: '' } }); }; return (
- {label && } + {label && ( +
+ +
+ )} {type === 'password' && (
@@ -58,13 +66,14 @@ const CommonInput = ({ label, helperText, disabled, required, onChange: change, rel="noreferrer" disabled={isDisabled} onClick={toggleEditing} + data-cy={`button-${generateCypressDataCy(isEditing ? 'Cancel' : 'Edit')}`} > {isEditing ? 'Cancel' : 'Edit'}
- + Encrypted diff --git a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx index 1834d4799d..978e69798f 100644 --- a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx +++ b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx @@ -14,14 +14,14 @@ const TextInput = ({ readOnly, ...restProps }) => { - const inputStyle = `tw-border-border-default placeholder:tw-text-text-placeholder tw-font-normal disabled:tw-bg-[#CCD1D5]/30 ${ + const inputStyle = `placeholder:tw-text-text-placeholder tw-font-normal disabled:tw-bg-[#CCD1D5]/30 ${ leadingIcon ? (size === 'small' ? 'tw-pl-[32px]' : 'tw-pl-[34px]') : 'tw-pl-[12px]' } ${trailingAction ? (size === 'small' ? 'tw-pr-[40px]' : 'tw-pr-[44px]') : 'tw-pr-[12px]'} ${ response === true ? '!tw-border-border-success-strong focus-visible:!tw-ring-0 focus-visible:!tw-ring-offset-0 focus-visible:!tw-border-border-success-strong' : response === false ? '!tw-border-border-danger-strong focus-visible:!tw-ring-0 focus-visible:!tw-ring-offset-0 focus-visible:!tw-border-border-danger-strong' - : '' + : 'tw-border-border-default' }`; return ( @@ -39,6 +39,7 @@ const TextInput = ({ size={size} placeholder={disabled && readOnly ? readOnly : placeholder} disabled={disabled} + response={response} {...restProps} className={inputStyle} /> diff --git a/frontend/src/components/ui/Input/EditableTitleInput/Index.jsx b/frontend/src/components/ui/Input/EditableTitleInput/Index.jsx index 62ae962970..0ca218e305 100644 --- a/frontend/src/components/ui/Input/EditableTitleInput/Index.jsx +++ b/frontend/src/components/ui/Input/EditableTitleInput/Index.jsx @@ -4,7 +4,7 @@ import { Input } from '../Input'; import { HelperMessage, ValidationMessage } from '../InputUtils/InputUtils'; import Tooltip from '../../Tooltip/Tooltip'; -const EditableTitleInput = ({ size, disabled, helperText, onChange, ...restProps }) => { +const EditableTitleInput = ({ size, disabled, helperText, onChange: change, readOnly, placeholder, ...restProps }) => { const inputRef = useRef(null); const [tooltipWidth, setTooltipWidth] = useState('auto'); const [isValid, setIsValid] = useState(null); @@ -17,7 +17,7 @@ const EditableTitleInput = ({ size, disabled, helperText, onChange, ...restProps setIsValid(validateObj.valid); setMessage(validateObj.message); } - onChange(e, validateObj); + change(e, validateObj); }; const inputStyle = `tw-border-transparent hover:tw-border-border-default tw-font-medium tw-pl-[12px] tw-pr-[12px] ${ @@ -35,7 +35,14 @@ const EditableTitleInput = ({ size, disabled, helperText, onChange, ...restProps return (
- + { +const Input = React.forwardRef(({ className, size, type, multiline, response, rows = 3, ...props }, ref) => { const [isPasswordVisible, setIsPasswordVisible] = React.useState(false); const isPasswordField = type === 'password'; @@ -13,19 +13,34 @@ const Input = React.forwardRef(({ className, size, type, ...props }, ref) => { } }; + const validationClass = response === true ? 'valid-textarea' : response === false ? 'invalid-textarea' : ''; + return ( - <> - - {isPasswordField && ( +
+ {multiline ? ( +