diff --git a/.github/workflows/cypress-platform.yml b/.github/workflows/cypress-platform.yml index c6a0db4be6..a29c46d495 100644 --- a/.github/workflows/cypress-platform.yml +++ b/.github/workflows/cypress-platform.yml @@ -12,91 +12,106 @@ env: 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-cypress-platform-ce') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') || - contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') + if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || + 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: + fail-fast: false matrix: - edition: >- - ${{ - contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "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('[]') - }} + edition: + - ${{ contains(github.event.pull_request.labels.*.name, 'run-cypress') && 'ce' || contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') && 'ce' || contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && 'ce' || '' }} + - ${{ contains(github.event.pull_request.labels.*.name, 'run-cypress') && 'ee' || contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') && 'ee' || '' }} + exclude: + - edition: "" steps: - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 18.18.2 - - - name: Set up Git authentication for private submodules + - name: Debug labels and matrix edition run: | - git config --global url."https://x-access-token:${{ secrets.CUSTOM_GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/" + echo "Labels: ${{ toJSON(github.event.pull_request.labels.*.name) }}" + echo "Matrix edition: ${{ matrix.edition }}" - - name: Checkout with Submodules + - name: Checkout uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.ref }} - - name: Checking out the correct branch for submodules EE + # Create Docker Buildx builder with platform configuration + - name: Set up Docker Buildx + run: | + mkdir -p ~/.docker/cli-plugins + curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx + chmod a+x ~/.docker/cli-plugins/docker-buildx + docker buildx create --name mybuilder --platform linux/arm64,linux/amd64 + docker buildx use mybuilder + + - name: Set DOCKER_CLI_EXPERIMENTAL + run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV + + - name: use mybuilder buildx + run: docker buildx use mybuilder + + - name: Docker Login + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Set SAFE_BRANCH_NAME + run: echo "SAFE_BRANCH_NAME=$(echo ${{ env.BRANCH_NAME }} | tr '/' '-')" >> $GITHUB_ENV + + - name: Build CE Docker image + if: matrix.edition == 'ce' + uses: docker/build-push-action@v4 + with: + context: . + file: docker/ce-production.Dockerfile + push: true + tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce + platforms: linux/amd64 + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build EE Docker image if: matrix.edition == 'ee' - run: | - git submodule update --init --recursive - git submodule foreach --recursive ' - git checkout ${{ env.BRANCH_NAME }} 2>/dev/null || git checkout main' - - - name: Set up Docker - uses: docker-practice/actions-setup-docker@master - - - name: Install and build dependencies - run: | - npm cache clean --force - npm install - npm install --prefix server - npm install --prefix frontend - npm run build:plugins - - - name: Local development setup - run: | - sudo docker network create tooljet - sudo docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_PORT=5432 -d postgres:13 - - - name: Run PostgREST Docker Container - run: | - sudo docker run -d --name postgrest --network tooljet -p 3001:3000 \ - -e PGRST_DB_URI="postgres://postgres:postgres@localhost:5432/tooljet" \ - -e PGRST_DB_ANON_ROLE="postgres" \ - -e PGRST_JWT_SECRET="r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" \ - -e PGRST_DB_PRE_CONFIG=postgrest.pre_config \ - postgrest/postgrest:v12.2.0 + uses: docker/build-push-action@v4 + with: + context: . + build-args: | + CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }} + BRANCH_NAME=${{ github.event.pull_request.head.ref }} + file: cypress-tests/cypress.Dockerfile + push: true + tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee + platforms: linux/amd64 + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - name: Set up environment variables run: | - echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'ee' || 'ce' }}" >> .env - echo "TOOLJET_HOST=http://localhost:8082" >> .env + echo "TOOLJET_EDITION=${{ matrix.edition }}" >> .env + echo "TOOLJET_HOST=http://localhost:3000" >> .env echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env echo "PG_DB=tooljet_development" >> .env echo "PG_USER=postgres" >> .env - echo "PG_HOST=localhost" >> .env + echo "PG_HOST=postgres" >> .env echo "PG_PASS=postgres" >> .env echo "PG_PORT=5432" >> .env echo "ENABLE_TOOLJET_DB=true" >> .env echo "TOOLJET_DB=tooljet_db" >> .env echo "TOOLJET_DB_USER=postgres" >> .env - echo "TOOLJET_DB_HOST=localhost" >> .env + echo "TOOLJET_DB_HOST=postgres" >> .env echo "TOOLJET_DB_PASS=postgres" >> .env echo "TOOLJET_DB_STATEMENT_TIMEOUT=60000" >> .env echo "TOOLJET_DB_RECONFIG=true" >> .env echo "PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" >> .env echo "PGRST_HOST=localhost:3001" >> .env echo "PGRST_DB_PRE_CONFIG=postgrest.pre_config" >> .env - echo "PGRST_DB_URI=postgres://postgres:postgres@localhost:5432/tooljet" >> .env + echo "PGRST_DB_URI=postgres://postgres:postgres@postgres/tooljet_db" >> .env echo "ENABLE_MARKETPLACE_FEATURE=true" >> .env echo "ENABLE_MARKETPLACE_DEV_MODE=true" >> .env echo "ENABLE_PRIVATE_APP_EMBED=true" >> .env @@ -105,29 +120,50 @@ jobs: echo "SSO_GIT_OAUTH2_CLIENT_ID=1234567890" >> .env echo "SSO_GIT_OAUTH2_CLIENT_SECRET=3346shfvkdjjsfkvxce32854e026a4531ed" >> .env - - name: Set up database - run: | - npm run --prefix server db:create - npm run --prefix server db:reset - sleep 5 + # Only add EE-specific env vars if edition is ee + if [ "${{ matrix.edition }}" = "ee" ]; then + echo "SSO_OPENID_NAME=tj-oidc-simulator" >> .env + echo "SSO_OPENID_CLIENT_ID=${{ secrets.SSO_OPENID_CLIENT_ID }}" >> .env + echo "SSO_OPENID_CLIENT_SECRET=${{ secrets.SSO_OPENID_CLIENT_SECRET }}" >> .env + echo "SSO_OPENID_WELL_KNOWN_URL=http://34.66.166.236:8080/.well-known/openid-configuration" >> .env + echo "LICENSE_KEY=${{ secrets.RENDER_LICENSE_KEY }}" >> .env + fi - - name: Start services + - name: Pulling the docker-compose file + run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data + + - name: Update docker-compose file run: | - cd plugins && npm start & - cd server && npm run start:dev & - cd frontend && npm start & + # Update docker-compose.yaml with the appropriate image based on edition + if [ "${{ matrix.edition }}" = "ce" ]; then + sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce|' docker-compose.yaml + elif [ "${{ matrix.edition }}" = "ee" ]; then + sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee|' docker-compose.yaml + fi + + - name: Install Docker Compose + run: | + curl -L "https://github.com/docker/compose/releases/download/v2.10.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + + - name: Run docker-compose file + run: docker-compose up -d + + - name: Checking containers + run: docker ps -a + + - name: docker logs + run: sudo docker logs Tooljet-app - name: Wait for the server to be ready run: | - timeout 300 bash -c ' - until curl --silent --fail http://localhost:8082; do + timeout 500 bash -c ' + until curl --silent --fail http://localhost:3000; do sleep 5 done' - - name: Postgres logs - run: docker logs postgrest - - - name: Create Cypress environment file + - name: Create Cypress environment file for CE + if: matrix.edition == 'ce' id: create-json uses: jsdaniell/create-json@1.1.2 with: @@ -135,13 +171,30 @@ jobs: json: ${{ secrets.CYPRESS_SECRETS }} dir: "./cypress-tests" - - name: Run Cypress tests + - name: Run Cypress tests for CE + if: matrix.edition == 'ce' uses: cypress-io/github-action@v6 with: working-directory: ./cypress-tests - config: "baseUrl=http://localhost:8082" + config: "baseUrl=http://localhost:3000" config-file: cypress-platform.config.js + - name: Create Cypress environment file for EE + if: matrix.edition == 'ee' + uses: jsdaniell/create-json@1.1.2 + with: + name: "cypress.env.json" + json: ${{ secrets.CYPRESS_EE_SECRETS }} + dir: "./cypress-tests" + + - name: Run Cypress tests for EE + if: matrix.edition == 'ee' + uses: cypress-io/github-action@v6 + with: + working-directory: ./cypress-tests + config: "baseUrl=http://localhost:3000" + config-file: cypress-ee-platform.config.js + - name: Capture Screenshots uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/maketplace-plugins-deploy.yml b/.github/workflows/maketplace-plugins-deploy.yml index 7f58a9b691..32c2965515 100644 --- a/.github/workflows/maketplace-plugins-deploy.yml +++ b/.github/workflows/maketplace-plugins-deploy.yml @@ -10,14 +10,12 @@ env: PR_NUMBER: ${{ github.event.number }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} - jobs: deploy-marketplace-plugin: if: ${{ github.event.action == 'labeled' && github.event.label.name == 'deploy-marketplace-plugin' }} runs-on: ubuntu-latest steps: - - name: Sync repo uses: actions/checkout@v3 @@ -49,28 +47,58 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECRET_MAR_ACCESS_KEY }} aws-region: us-east-2 - - name: Install and build dependencies + - name: Install and build dependencies in order run: | - cd marketplace && npm install && npm run build --workspaces - continue-on-error: true - - - name: Build marketplace plugins - run: | - cd marketplace && AWS_BUCKET=tooljet-plugins-stage node scripts/upload-to-s3.js + cd marketplace + echo "šŸ”§ Installing all workspace dependencies" + npm install - - name: Comment deployment URL + echo "šŸ—ļø Building 'common' plugin first" + npm run build --workspace=plugins/common || exit 1 + + echo "šŸ” Building all remaining plugins" + PLUGINS=$(ls plugins | grep -v '^common$') + for plugin in $PLUGINS; do + echo "šŸ”Ø Building plugin: $plugin" + npm run build --workspace=plugins/$plugin || exit 1 + done + + + - name: Build marketplace plugins and capture summary + run: | + cd marketplace + echo "šŸš€ Uploading to S3" + AWS_BUCKET=tooljet-plugins-stage node scripts/upload-to-s3.js | tee upload_summary.log + + - name: Extract upload summary + id: upload_summary + run: | + SUMMARY=$(awk '/UPLOAD SUMMARY/,0' marketplace/upload_summary.log) + echo "UPLOAD_SUMMARY<> $GITHUB_ENV + echo "$SUMMARY" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Comment on success + if: success() uses: actions/github-script@v5 with: - github-token: ${{secrets.GITHUB_TOKEN}} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | + const runId = process.env.GITHUB_RUN_ID; + const runUrl = `https://github.com/${{ github.repository }}/actions/runs/${runId}`; + const summary = process.env.UPLOAD_SUMMARY; + const body = `Marketplace Plugin added to stage bucket\n\nšŸ” [View Deployment Logs & Summary](${runUrl})\n\n\`\`\`\n${summary}\n\`\`\``; + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'Marketplace Plugin added to stage bucket' - }) + body + }); - - uses: actions/github-script@v6 + - name: Label update on success + if: success() + uses: actions/github-script@v6 with: script: | try { @@ -90,3 +118,19 @@ jobs: repo: context.repo.repo, labels: ['marketplace-plugin-deployed'] }) + + - name: Comment on failure + if: failure() + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const runId = process.env.GITHUB_RUN_ID; + const runUrl = `https://github.com/${{ github.repository }}/actions/runs/${runId}`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `āŒ Marketplace Plugin deployment failed.\n\nšŸ” [View Deployment Logs & Summary](${runUrl})` + }); \ No newline at end of file diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index c5cb025cba..5d35c9dd03 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -1,7 +1,9 @@ name: Render review deploy on: pull_request_target: - types: [labeled, unlabeled, closed] + types: [labeled, unlabeled, closed, synchronize, opened] + issue_comment: + types: [created, edited, deleted] env: PR_NUMBER: ${{ github.event.number }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} @@ -1153,3 +1155,234 @@ jobs: # } catch (e) { # console.log(e) # } + + redeploy-review-app: + if: ${{ github.event.action == 'synchronize' || github.event.action == 'opened' }} + runs-on: ubuntu-latest + steps: + - name: Get PR labels + id: get_labels + uses: actions/github-script@v6 + with: + script: | + const labels = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + return labels.data.map(l => l.name); + + - name: Redeploy CE review app if active + if: contains(steps.get_labels.outputs.result, 'active-ce-review-app') + id: redeploy_ce + env: + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + PR_NUMBER: ${{ github.event.number }} + run: | + SERVICE_ID=$(curl --request GET \ + --url "https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${PR_NUMBER}&limit=1" \ + --header 'accept: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY" | jq -r '.[0].service.id') + DEPLOY_RESPONSE=$(curl --request POST \ + --url "https://api.render.com/v1/services/$SERVICE_ID/deploys" \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY" \ + --data '{"clearCache":"clear"}') + DEPLOY_ID=$(echo $DEPLOY_RESPONSE | jq -r '.id') + echo "SERVICE_ID=$SERVICE_ID" >> $GITHUB_ENV + echo "DEPLOY_ID=$DEPLOY_ID" >> $GITHUB_ENV + + + - name: Redeploy EE review app if active + if: contains(steps.get_labels.outputs.result, 'active-ee-review-app') + id: redeploy_ee + env: + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + PR_NUMBER: ${{ github.event.number }} + run: | + SERVICE_ID=$(curl --request GET \ + --url "https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${PR_NUMBER}&limit=1" \ + --header 'accept: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY" | jq -r '.[0].service.id') + DEPLOY_RESPONSE=$(curl --request POST \ + --url "https://api.render.com/v1/services/$SERVICE_ID/deploys" \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY" \ + --data '{"clearCache":"clear"}') + DEPLOY_ID=$(echo $DEPLOY_RESPONSE | jq -r '.id') + echo "SERVICE_ID=$SERVICE_ID" >> $GITHUB_ENV + echo "DEPLOY_ID=$DEPLOY_ID" >> $GITHUB_ENV + + + render-bot-check-deployment: + runs-on: ubuntu-latest + if: github.event.action == 'labeled' && github.event.label.name == 'render-check-deployment' + steps: + - name: Get PR labels + id: get_labels + uses: actions/github-script@v6 + with: + script: | + const labels = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + return labels.data.map(l => l.name); + + - name: Fetch CE service and deploy ID + run: | + response=$(curl --silent --request GET \ + --url "https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${PR_NUMBER}&limit=1" \ + --header 'accept: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY") + + SERVICE_ID=$(echo "$response" | jq -r 'if type=="array" and length > 0 then .[0].service.id else empty end') + + if [[ -z "$SERVICE_ID" ]]; then + echo "No CE service found for PR #$PR_NUMBER. Skipping deployment ID fetch." + exit 0 + fi + + response_deploy=$(curl --silent --request GET \ + --url "https://api.render.com/v1/services/$SERVICE_ID/deploys?limit=1" \ + --header 'accept: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY") + + DEPLOY_ID=$(echo "$response_deploy" | jq -r 'if type=="array" and length > 0 then .[0].deploy.id else empty end') + + echo "CE_SERVICE_ID=$SERVICE_ID" >> $GITHUB_ENV + echo "CE_DEPLOY_ID=$DEPLOY_ID" >> $GITHUB_ENV + env: + PR_NUMBER: ${{ env.PR_NUMBER }} + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + + - name: Comment CE deployment details + uses: actions/github-script@v6 + env: + PR_NUMBER: ${{ env.PR_NUMBER }} + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prNumber = process.env.PR_NUMBER; + const apiKey = process.env.RENDER_API_KEY; + + const ceServiceRes = await fetch(`https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${prNumber}&limit=1`, { + headers: { + 'accept': 'application/json', + 'authorization': `Bearer ${apiKey}` + } + }); + const ceServices = await ceServiceRes.json(); + const ceServiceId = ceServices[0]?.service?.id || null; + + let ceInfo = 'No active CE review app deployment found.'; + if (ceServiceId) { + const deployRes = await fetch(`https://api.render.com/v1/services/${ceServiceId}/deploys?limit=1`, { + headers: { + 'accept': 'application/json', + 'authorization': `Bearer ${apiKey}` + } + }); + const deployData = await deployRes.json(); + const deploy = deployData[0]?.deploy || {}; + const ceCommit = deploy.commit || {}; + const status = deploy.status || 'unknown'; + ceInfo = `### Community Edition\n- App: https://tooljet-ce-pr-${prNumber}.onrender.com\n- Dashboard: https://dashboard.render.com/web/${ceServiceId}\n- Commit: ${ceCommit.id || ''}\n- Message: ${ceCommit.message || ''}\n- Status: ${status}`; + } + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: ceInfo + }); + + - name: Fetch EE service and deploy ID + run: | + response=$(curl --silent --request GET \ + --url "https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${PR_NUMBER}&limit=1" \ + --header 'accept: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY") + + SERVICE_ID=$(echo "$response" | jq -r 'if type=="array" and length > 0 then .[0].service.id else empty end') + + if [[ -z "$SERVICE_ID" ]]; then + echo "No EE service found for PR #$PR_NUMBER. Skipping deployment ID fetch." + exit 0 + fi + + response_deploy=$(curl --silent --request GET \ + --url "https://api.render.com/v1/services/$SERVICE_ID/deploys?limit=1" \ + --header 'accept: application/json' \ + --header "authorization: Bearer $RENDER_API_KEY") + + DEPLOY_ID=$(echo "$response_deploy" | jq -r 'if type=="array" and length > 0 then .[0].deploy.id else empty end') + + echo "EE_SERVICE_ID=$SERVICE_ID" >> $GITHUB_ENV + echo "EE_DEPLOY_ID=$DEPLOY_ID" >> $GITHUB_ENV + env: + PR_NUMBER: ${{ env.PR_NUMBER }} + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + + - name: Comment EE deployment details + uses: actions/github-script@v6 + env: + PR_NUMBER: ${{ env.PR_NUMBER }} + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prNumber = process.env.PR_NUMBER; + const apiKey = process.env.RENDER_API_KEY; + + const eeServiceRes = await fetch(`https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${prNumber}&limit=1`, { + headers: { + 'accept': 'application/json', + 'authorization': `Bearer ${apiKey}` + } + }); + const eeServices = await eeServiceRes.json(); + const eeServiceId = eeServices[0]?.service?.id || null; + + let eeInfo = 'No active EE review app deployment found.'; + if (eeServiceId) { + const deployRes = await fetch(`https://api.render.com/v1/services/${eeServiceId}/deploys?limit=1`, { + headers: { + 'accept': 'application/json', + 'authorization': `Bearer ${apiKey}` + } + }); + const deployData = await deployRes.json(); + const deploy = deployData[0]?.deploy || {}; + const eeCommit = deploy.commit || {}; + const status = deploy.status || 'unknown'; + eeInfo = `### Enterprise Edition\n- App: https://tooljet-ee-pr-${prNumber}.onrender.com\n- Dashboard: https://dashboard.render.com/web/${eeServiceId}\n- Commit: ${eeCommit.id || ''}\n- Message: ${eeCommit.message || ''}\n- Status: ${status}`; + } + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: eeInfo + }); + + - name: Remove label + if: contains(steps.get_labels.outputs.result, 'render-check-deployment') + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'render-check-deployment' + }) + } catch (e) { + console.log(e) + } diff --git a/.version b/.version index 4eba2a62eb..f982feb41b 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.13.0 +3.14.0 diff --git a/cypress-tests/cypress-ee-platform.config.js b/cypress-tests/cypress-ee-platform.config.js new file mode 100644 index 0000000000..02b8c1d952 --- /dev/null +++ b/cypress-tests/cypress-ee-platform.config.js @@ -0,0 +1,114 @@ +const { defineConfig } = require("cypress"); +const { rmdir } = require("fs"); +const fs = require("fs"); +const XLSX = require("node-xlsx"); +const pg = require("pg"); +const path = require("path"); +const pdf = require("pdf-parse"); + +const environments = { + 'run-cypress-platform': { + baseUrl: "http://localhost:3000", + configFile: "cypress-platform.config.js" + }, + 'run-cypress-platform-subpath': { + baseUrl: "http://localhost:3000/apps", + configFile: "cypress-platform.config.js" + }, + 'run-cypress-platform-proxy': { + baseUrl: "http://localhost:4001", + configFile: "cypress-platform.config.js" + }, + 'run-cypress-platform-proxy-subpath': { + baseUrl: "http://localhost:4001/apps", + configFile: "cypress-platform.config.js" + } +}; + +const githubLabel = process.env.GITHUB_LABEL || 'run-cypress-platform'; +const environment = environments[githubLabel]; + +module.exports = defineConfig({ + execTimeout: 1800000, + defaultCommandTimeout: 30000, + requestTimeout: 30000, + pageLoadTimeout: 30000, + responseTimeout: 30000, + viewportWidth: 1440, + viewportHeight: 960, + chromeWebSecurity: false, + trashAssetsBeforeRuns: true, + e2e: { + setupNodeEvents (on, config) { + config.baseUrl = environment.baseUrl; + + on("task", { + readPdf (pathToPdf) { + return new Promise((resolve) => { + const pdfPath = path.resolve(pathToPdf); + let dataBuffer = fs.readFileSync(pdfPath); + pdf(dataBuffer).then(function ({ text }) { + resolve(text); + }); + }); + }, + }); + + on("task", { + readXlsx (filePath) { + return new Promise((resolve, reject) => { + try { + let dataBuffer = fs.readFileSync(filePath); + const jsonData = XLSX.parse(dataBuffer); + resolve(jsonData[0]["data"].toString()); + } catch (e) { + reject(e); + } + }); + }, + }); + + on("task", { + deleteFolder (folderName) { + return new Promise((resolve, reject) => { + rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => { + if (err) { + console.error(err); + return reject(err); + } + resolve(null); + }); + }); + }, + }); + + on("task", { + dbConnection ({ dbconfig, sql }) { + const client = new pg.Pool(dbconfig); + return client.query(sql); + }, + }); + + return require("./cypress/plugins/index.js")(on, config); + }, + downloadsFolder: "cypress/downloads", + experimentalRunAllSpecs: true, + experimentalModfyObstructiveThirdPartyCode: true, + baseUrl: environment.baseUrl, + configFile: environment.configFile, + specPattern: [ + "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", + "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", + "cypress/e2e/happyPath/platform/eeTestcases/**/*.cy.js", + ], + numTestsKeptInMemory: 1, + redirectionLimit: 15, + experimentalMemoryManagement: true, + video: false, + videoUploadOnPasses: false, + retries: { + runMode: 2, + openMode: 0, + }, + }, +}); \ No newline at end of file diff --git a/cypress-tests/cypress-platform.config.js b/cypress-tests/cypress-platform.config.js index b565a0c1d1..6b1954140a 100644 --- a/cypress-tests/cypress-platform.config.js +++ b/cypress-tests/cypress-platform.config.js @@ -8,7 +8,7 @@ const pdf = require("pdf-parse"); const environments = { 'run-cypress-platform': { - baseUrl: "http://localhost:8082", + baseUrl: "http://localhost:3000", configFile: "cypress-platform.config.js" }, 'run-cypress-platform-subpath': { diff --git a/cypress-tests/cypress.Dockerfile b/cypress-tests/cypress.Dockerfile new file mode 100644 index 0000000000..373b3bafd3 --- /dev/null +++ b/cypress-tests/cypress.Dockerfile @@ -0,0 +1,189 @@ +FROM node:18.18.2-buster AS builder +# Fix for JS heap limit allocation issue +ENV NODE_OPTIONS="--max-old-space-size=4096" + +RUN mkdir -p /app + +WORKDIR /app + +ARG CUSTOM_GITHUB_TOKEN +ARG BRANCH_NAME + +# 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 +RUN git config --global http.postBuffer 524288000 +RUN git clone https://github.com/ToolJet/ToolJet.git . + +# The branch name needs to be changed the branch with modularisation in CE repo +RUN git checkout ${BRANCH_NAME} + +RUN git submodule update --init --recursive + +# Checkout the same branch in submodules if it exists, otherwise stay on default branch +RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true' + +# Scripts for building +COPY ./package.json ./package.json + +# Build plugins +COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ +RUN npm --prefix plugins install +COPY ./plugins/ ./plugins/ +RUN NODE_ENV=production npm --prefix plugins run build +RUN npm --prefix plugins prune --production + +ENV TOOLJET_EDITION=ee + +# Build frontend +COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/ +RUN npm --prefix frontend install +COPY ./frontend/ ./frontend/ +RUN npm --prefix frontend run build --production +RUN npm --prefix frontend prune --production + +ENV NODE_ENV=production +ENV TOOLJET_EDITION=ee + +# Build server +COPY ./server/package.json ./server/package-lock.json ./server/ +RUN npm --prefix server install +COPY ./server/ ./server/ +RUN npm install -g @nestjs/cli +RUN npm --prefix server run build + +FROM node:18.18.2-bullseye + +RUN apt-get update -yq \ + && apt-get install curl wget gnupg zip -yq \ + && apt-get install -yq build-essential \ + && apt -y install redis \ + && apt-get clean -y + +# copy postgrest executable +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 freetds-dev libaio1 wget supervisor + +# Install Instantclient Basic Light Oracle and Dependencies +WORKDIR /opt/oracle +RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \ + wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \ + unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig +# Set the Instant Client library paths +ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}" + +WORKDIR / + +# copy npm scripts +COPY --from=builder /app/package.json ./app/package.json +# copy plugins dependencies +COPY --from=builder /app/plugins/dist ./app/plugins/dist +COPY --from=builder /app/plugins/client.js ./app/plugins/client.js +COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules +COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common +COPY --from=builder /app/plugins/package.json ./app/plugins/package.json +# copy frontend build +COPY --from=builder /app/frontend/build ./app/frontend/build +# copy server build +COPY --from=builder /app/server/package.json ./app/server/package.json +COPY --from=builder /app/server/.version ./app/server/.version +COPY --from=builder /app/server/ee/keys ./app/server/ee/keys +COPY --from=builder /app/server/node_modules ./app/server/node_modules +COPY --from=builder /app/server/templates ./app/server/templates +COPY --from=builder /app/server/scripts ./app/server/scripts +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 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:redis] \n" \ + "command=redis-server /etc/redis/redis.conf \n" \ + "user=redis \n" \ + "autostart=true \n" \ + "autorestart=true \n" \ + "stderr_logfile=/var/log/redis/redis-server.log \n" \ + "stdout_logfile=/var/log/redis/redis-server.log \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=3000 \ + 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=postgres \ + PG_PASS=postgres \ + PG_HOST=localhost \ + ENABLE_TOOLJET_DB=true \ + TOOLJET_DB_HOST=localhost \ + TOOLJET_DB_USER=postgres \ + TOOLJET_DB_PASS=postgres \ + TOOLJET_DB=tooljet_db \ + PGRST_HOST=http://localhost:3001 \ + PGRST_SERVER_PORT=3001 \ + PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \ + PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ + PGRST_DB_PRE_CONFIG=postgrest.pre_config \ + REDIS_HOST=localhost \ + REDIS_PORT=6379 \ + REDIS_USER= \ + REDIS_PASSWORD= \ + ORM_LOGGING=true \ + DEPLOYMENT_PLATFORM=docker:local \ + HOME=/home/appuser \ + TERM=xterm + + +RUN chmod +x ./server/scripts/preview.sh +# Set the entrypoint +ENTRYPOINT ["./server/scripts/preview.sh"] diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index c625e6cc33..62d72f7cdc 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -52,7 +52,7 @@ Cypress.Commands.add("apiCreateGDS", (url, name, kind, options) => { log: false; } expect(response.status).to.equal(201); - Cypress.env(`${kind}`, response.body.id); + Cypress.env(`${name}`, response.body.id); Cypress.log({ name: "Create Data Source", @@ -80,13 +80,14 @@ Cypress.Commands.add("apiFetchDataSourcesId", () => { Cypress.log({ name: "DS Fetch", displayName: "Data Sources Fetched", - message: dataSources.map(ds => `\nKind: '${ds.kind}', Name: '${ds.id}'`).join(','), + message: dataSources + .map((ds) => `\nKind: '${ds.kind}', Name: '${ds.id}'`) + .join(","), }); }); }); }); - Cypress.Commands.add("apiCreateApp", (appName = "testApp") => { cy.window({ log: false }).then((win) => { win.localStorage.setItem("walkthroughCompleted", "true"); @@ -168,7 +169,6 @@ Cypress.Commands.add( Cypress.env("editingVersionId", responseData.editing_version.id); Cypress.env("environmentId", responseData.editorEnvironment.id); - }); cy.get(componentSelector, { timeout: 10000 }); } @@ -221,21 +221,21 @@ Cypress.Commands.add( const requestBody = envVar === "Enterprise" ? { - email: userEmail, - firstName: userName, - groups: [], - lastName: "", - role: userRole, - userMetadata: metaData, - } + email: userEmail, + firstName: userName, + groups: [], + lastName: "", + role: userRole, + userMetadata: metaData, + } : { - email: userEmail, - firstName: userName, - groups: [], - lastName: "", - role: userRole, - userMetadata: metaData, - }; + email: userEmail, + firstName: userName, + groups: [], + lastName: "", + role: userRole, + userMetadata: metaData, + }; cy.getCookie("tj_auth_token").then((cookie) => { cy.request( @@ -289,7 +289,9 @@ Cypress.Commands.add("apiAddQuery", (queryName, query, dataQueryId) => { Cypress.Commands.add( "apiAddQueryToApp", (queryName, options, dsName, dsKind) => { - cy.log(`${Cypress.env("server_host")}/api/data-queries/data-sources/${Cypress.env(dsKind)}/versions/${Cypress.env("editingVersionId")}`) + cy.log( + `${Cypress.env("server_host")}/api/data-queries/data-sources/${Cypress.env(dsKind)}/versions/${Cypress.env("editingVersionId")}` + ); cy.getCookie("tj_auth_token", { log: false }).then((cookie) => { const authToken = `tj_auth_token=${cookie.value}`; const workspaceId = Cypress.env("workspaceId"); @@ -737,3 +739,55 @@ Cypress.Commands.add("apiGetAppData", (appId = Cypress.env("appId")) => { }); }); }); + +Cypress.Commands.add("apiDeleteGDS", (name) => { + const dataSourceId = Cypress.env(`${name}`); + + cy.getCookie("tj_auth_token").then((cookie) => { + cy.request({ + method: "DELETE", + url: `${Cypress.env("server_host")}/api/data-sources/${dataSourceId}`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }, + failOnStatusCode: false, + }).then((response) => { + console.log("Delete response:", response); + + expect(response.status, "Delete status code").to.eq(200); + + Cypress.log({ + name: "Delete Data Source", + displayName: "Data source deleted", + message: `Name: '${name}' | ID: '${dataSourceId}'`, + }); + }); + }); +}); + +Cypress.Commands.add( + "apiUpdateGDS", + ({ name, options, envName = "development" }) => { + cy.getAuthHeaders().then((headers) => { + cy.apiGetEnvironments().then((environments) => { + const environment = environments.find((env) => env.name === envName); + const environmentId = environment.id; + const dataSourceId = Cypress.env(`${name}`); + + cy.request({ + method: "PUT", + url: `${Cypress.env("server_host")}/api/data-sources/${dataSourceId}?environment_id=${environmentId}`, + headers: headers, + body: { + name: name, + options: options, + }, + }).then((response) => { + expect(response.status).to.equal(200); + cy.log(`Datasource "${name}" updated successfully.`); + }); + }); + }); + } +); diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index d242eb1895..3bb63cc926 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -226,9 +226,9 @@ Cypress.Commands.add( .invoke("text") .then((text) => { cy.wrap(subject).realType(createBackspaceText(text)), - { - delay: 0, - }; + { + delay: 0, + }; }); } ); @@ -429,7 +429,6 @@ Cypress.Commands.add("visitSlug", ({ actualUrl }) => { }); }); - Cypress.Commands.add("releaseApp", () => { if (Cypress.env("environment") !== "Community") { cy.get(commonEeSelectors.promoteButton).click(); @@ -549,7 +548,7 @@ Cypress.Commands.add("installMarketplacePlugin", (pluginName) => { } }); - function installPlugin (pluginName) { + function installPlugin(pluginName) { cy.get('[data-cy="-list-item"]').eq(1).click(); cy.wait(1000); @@ -605,3 +604,20 @@ Cypress.Commands.add("uninstallMarketplacePlugin", (pluginName) => { }); }); }); + +Cypress.Commands.add( + "verifyRequiredFieldValidation", + (fieldName, expectedColor) => { + cy.get(commonSelectors.textField(fieldName)).should( + "have.css", + "border-color", + expectedColor + ); + cy.get(commonSelectors.labelFieldValidation(fieldName)) + .should("be.visible") + .and("have.text", `${fieldName} is required`); + cy.get(commonSelectors.labelFieldAlert(fieldName)) + .should("be.visible") + .and("have.text", `${fieldName} is required`); + } +); diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index ec104d67bc..7a238f56c7 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -1,5 +1,5 @@ export const cyParamName = (paramName = "") => { - return paramName.toLowerCase().replace(/\s+/g, "-"); + return String(paramName).toLowerCase().replace(/\s+/g, "-"); }; export const commonSelectors = { @@ -278,6 +278,16 @@ export const commonSelectors = { defaultModalTitle: '[data-cy="modal-title"]', workspaceConstantsIcon: '[data-cy="icon-workspace-constants"]', confirmationButton: '[data-cy="confirmation-button"]', + + textField: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-text-field"]`; + }, + labelFieldValidation: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-is-required-validation-label"]`; + }, + labelFieldAlert: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-is-required-field-alert-text"]`; + }, }; export const commonWidgetSelector = { diff --git a/cypress-tests/cypress/constants/selectors/dataSource.js b/cypress-tests/cypress/constants/selectors/dataSource.js index 242747e8b1..86f5a24c58 100644 --- a/cypress-tests/cypress/constants/selectors/dataSource.js +++ b/cypress-tests/cypress/constants/selectors/dataSource.js @@ -28,7 +28,7 @@ export const dataSourceSelector = { buttonTestConnection: '[data-cy="test-connection-button"]', connectionFailedText: '[data-cy="test-connection-failed-text"]', buttonSave: '[data-cy="db-connection-save-button"] > .tj-base-btn', - dangerAlertNotSupportSSL: '.go3958317564', + dangerAlertNotSupportSSL: ".go3958317564", passwordTextField: '[data-cy="password-text-field"]', textConnectionVerified: '[data-cy="test-connection-verified-text"]', @@ -101,7 +101,48 @@ export const dataSourceSelector = { unSavedModalTitle: '[data-cy="unsaved-changes-title"]', eventQuerySelectionField: '[data-cy="query-selection-field"]', connectionAlertText: '[data-cy="connection-alert-text"]', + requiredIndicator: '[data-cy="required-indicator"]', + informationIcon: '[data-cy="information-icon"]', deleteDSButton: (datasourceName) => { - return `[data-cy="${cyParamName(datasourceName)}-delete-button"]` + return `[data-cy="${cyParamName(datasourceName)}-delete-button"]`; + }, + labelFieldName: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-field-label"]`; + }, + dataSourceNameButton: (dataSourceName) => { + return `[data-cy="${cyParamName(dataSourceName)}-button"]`; + }, + dropdownLabel: (label) => { + return `[data-cy="${cyParamName(label)}-dropdown-label"]`; + }, + textField: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-text-field"]`; + }, + subSection: (header) => { + return `[data-cy="${cyParamName(header)}-section"]`; + }, + toggleInput: (toggleName) => { + return `[data-cy="${cyParamName(toggleName)}-toggle-input"]`; + }, + button: (buttonName) => { + return `[data-cy="button-${cyParamName(buttonName)}"]`; + }, + keyInputField: (header, index) => { + return `[data-cy="${cyParamName(header)}-key-input-field-${cyParamName(index)}"]`; + }, + valueInputField: (header, index) => { + return `[data-cy="${cyParamName(header)}-value-input-field-${cyParamName(index)}"]`; + }, + deleteButton: (header, index) => { + return `[data-cy="${cyParamName(header)}-delete-button-${cyParamName(index)}"]`; + }, + addMoreButton: (header) => { + return `[data-cy="${cyParamName(header)}-add-button"]`; + }, + dropdownField: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-select-dropdown"]`; + }, + labelFieldValidation: (fieldName) => { + return `[data-cy="${cyParamName(fieldName)}-is-required-validation-label"]`; }, }; diff --git a/cypress-tests/cypress/constants/selectors/postgreSql.js b/cypress-tests/cypress/constants/selectors/postgreSql.js index 4f38357961..112da90779 100644 --- a/cypress-tests/cypress/constants/selectors/postgreSql.js +++ b/cypress-tests/cypress/constants/selectors/postgreSql.js @@ -87,6 +87,8 @@ export const postgreSqlSelector = { recordsInputField: '[data-cy="records-input-field"]', eventQuerySelectionField: '[data-cy="query-selection-field"]', + sslToggleInput: '[data-cy="ssl-enabled-toggle-input"]', + labelEncryptedText: '[data-cy="encrypted-text"]', }; export const airTableSelector = { diff --git a/cypress-tests/cypress/constants/texts/airTable.js b/cypress-tests/cypress/constants/texts/airTable.js index 44df3cf9e1..1604fd7590 100644 --- a/cypress-tests/cypress/constants/texts/airTable.js +++ b/cypress-tests/cypress/constants/texts/airTable.js @@ -1,6 +1,7 @@ export const airtableText = { - airtable: "Airtable", - cypressairtable: "cypress-Airtable", - ApiKey: "Personal access token", - apikeyPlaceholder: "**************", - }; \ No newline at end of file + airtable: "Airtable", + cypressairtable: "cypress-Airtable", + ApiKey: "Personal access token", + apikeyPlaceholder: "**************", + invalidAccessToken: "Authentication failed: Invalid personal access token", +}; diff --git a/cypress-tests/cypress/constants/texts/postgreSql.js b/cypress-tests/cypress/constants/texts/postgreSql.js index 9db745b58d..d5c85c197b 100644 --- a/cypress-tests/cypress/constants/texts/postgreSql.js +++ b/cypress-tests/cypress/constants/texts/postgreSql.js @@ -17,13 +17,17 @@ export const postgreSqlText = { allCloudStorage: "Cloud Storages (4)", postgreSQL: "PostgreSQL", + labelConnectionType: "Connection type", + manualConnectionOption: "Manual connection", + connectionStringOption: "Connection string", labelHost: "Host", labelPort: "Port", labelSSL: "SSL", labelDbName: "Database name", labelUserName: "Username", labelPassword: "Password", - label: "Encrypted", + labelEncrypted: "Encrypted", + labelConnectionOptions: "Connection options", sslCertificate: "SSL certificate", whiteListIpText: "Please white-list our IP address if the data source is not publicly accessible", @@ -74,6 +78,8 @@ export const postgreSqlText = { guiOptionBulkUpdate: "Bulk update using primary key", buttonTextTestConnection: "Test connection", + editButtonText: "Edit", + unableAcquireConnectionAlertText: "Unable to acquire a connection", tabAdvanced: "Advanced", labelNoEventhandler: "No event handlers", diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js index b9bce11f9a..51c3e00ef1 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/appTitle.cy.js @@ -1,34 +1,5 @@ import { fake } from "Fixtures/fake"; -import { textInputText } from "Texts/textInput"; -import { commonWidgetText, widgetValue, customValidation } from "Texts/common"; -import { commonSelectors, commonWidgetSelector } from "Selectors/common"; -import { buttonText } from "Texts/button"; -import { - verifyControlComponentAction, - randomString, -} from "Support/utils/editor/textInput"; -import { - openAccordion, - verifyAndModifyParameter, - openEditorSidebar, - verifyAndModifyToggleFx, - addDefaultEventHandler, - verifyComponentValueFromInspector, - selectColourFromColourPicker, - verifyBoxShadowCss, - verifyLayout, - verifyTooltip, - editAndVerifyWidgetName, - verifyPropertiesGeneralAccordion, - verifyStylesGeneralAccordion, - randomNumber, - closeAccordions, -} from "Support/utils/commonWidget"; -import { - selectCSA, - selectEvent, - addSupportCSAData, -} from "Support/utils/events"; +import { commonWidgetSelector } from "Selectors/common"; describe("Editor title", () => { const data = {}; @@ -42,10 +13,10 @@ describe("Editor title", () => { afterEach(() => { cy.apiDeleteApp(); }); - it("should verify titles", () => { + it.skip("should verify titles", () => { cy.url().should("include", "/tooljets-workspace"); - // cy.title().should("eq", "Dashboard | ToolJet"); - cy.title().should("eq", "ToolJet"); + cy.title().should("eq", "Dashboard | ToolJet"); + // cy.title().should("eq", "ToolJet"); cy.log(data.appName); @@ -57,7 +28,7 @@ describe("Editor title", () => { cy.url().should("include", `/applications/${Cypress.env("appId")}`); // cy.title().should("eq", `${data.appName} | ToolJet`); - // cy.title().should("eq", `Preview - ${data.appName} | ToolJet`); + cy.title().should("eq", `Preview - ${data.appName} | ToolJet`); cy.go("back"); cy.releaseApp(); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js index 263825a0a1..56ff165f57 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/button.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Button Component Tests', () => { @@ -75,22 +75,21 @@ describe('Button Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Button-App`); cy.openApp(); - cy.dragAndDropWidget("Button", 50, 50); + cy.dragAndDropWidget("Button", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); it('should verify all the exposed values on inspector', () => { cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); - openNode("components"); - openAndVerifyNode("button1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("button1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); - it.skip('should verify all the events from the button', () => { + it('should verify all the events from the button', () => { const events = [ { event: "On hover", message: "On hover Event" }, { event: "On Click", message: "On Click Event" }, diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js index 5ff7f869fe..521ac89867 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/checkbox.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Checkbox Component Tests', () => { @@ -83,7 +83,7 @@ describe('Checkbox Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Checkbox-App`); cy.openApp(); - cy.dragAndDropWidget("Checkbox", 50, 50); + cy.dragAndDropWidget("Checkbox", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); @@ -92,8 +92,8 @@ describe('Checkbox Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("checkbox1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("checkbox1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js index 53dcf30a54..d9ee3d8b56 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/dropdown.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Dropdown Component Tests', () => { @@ -101,7 +101,7 @@ describe('Dropdown Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("dropdown1", exposedValues, verifyValue); + openAndVerifyNode("dropdown1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js index 08a84e5fc3..a892251502 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/globalActions.skip.js @@ -15,7 +15,7 @@ import { deleteDownloadsFolder } from "Support/utils/common"; import { resizeQueryPanel } from "Support/utils/dataSource"; -import { openNode, verifyNodeData, verifyValue } from "Support/utils/inspector"; +import { openNode, verifyNodeData } from "Support/utils/inspector"; import { addNewPage } from "Support/utils/multipage"; @@ -49,11 +49,11 @@ describe("Global Actions", () => { verifyNodeData("variables", "Object", "1 entry "); openNode("variables", 0); - verifyValue("var", "String", `"test"`); + verifyNodeData("var", "String", `"test"`); openNode("page"); openNode("variables", 1); - verifyValue("pageVar", "String", `"pageTest"`); + verifyNodeData("pageVar", "String", `"pageTest"`); addInputOnQueryField( "runjs", diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js index a917ef77e6..0097c7d738 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/multiselect.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Multiselect Component Tests', () => { @@ -104,7 +104,7 @@ describe('Multiselect Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("multiselect1", exposedValues, verifyValue); + openAndVerifyNode("multiselect1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js index dddf1931a6..93aff8c0cb 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/numberInput.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Number Input Component Tests', () => { @@ -86,7 +86,7 @@ describe('Number Input Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Numberinput-App`); cy.openApp(); - cy.dragAndDropWidget("Number Input", 50, 50); + cy.dragAndDropWidget("Number Input", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); @@ -95,8 +95,8 @@ describe('Number Input Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("numberinput1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("numberinput1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js index edfd8f04ef..5d1b377a88 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/passwordInput.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Password Input Component Tests', () => { @@ -95,7 +95,7 @@ describe('Password Input Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("passwordinput1", exposedValues, verifyValue); + openAndVerifyNode("passwordinput1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js index d7b277d193..e571cb11ec 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/textInput.cy.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('Text Input Component Tests', () => { @@ -27,14 +27,14 @@ describe('Text Input Component Tests', () => { "key": "setBlur", "type": "Function" }, - { - "key": "disable", - "type": "Function" - }, - { - "key": "visibility", - "type": "Function" - }, + // { + // "key": "disable", + // "type": "Function" + // }, + // { + // "key": "visibility", + // "type": "Function" + // }, { "key": "setVisibility", "type": "Function" @@ -94,17 +94,17 @@ describe('Text Input Component Tests', () => { cy.apiLogin(); cy.apiCreateApp(`${fake.companyName}-Textinput-App`); cy.openApp(); - cy.dragAndDropWidget("Text Input", 50, 50); + cy.dragAndDropWidget("Text Input", 500, 500); cy.get('[data-cy="query-manager-toggle-button"]').click(); }); - it.skip('should verify all the exposed values on inspector', () => { + it('should verify all the exposed values on inspector', () => { cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("textinput1", exposedValues, verifyValue); - verifyNodes(functions, verifyfunctions); + openAndVerifyNode("textinput1", exposedValues, verifyNodeData); + verifyNodes(functions, verifyNodeData); //id is pending }); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js index 3c97812ec9..9fc577adc1 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/componentsBasics/toggleSwitch.skip.js @@ -5,7 +5,7 @@ import { verifyCSA } from "Support/utils/editor/textInput"; import { addMultiEventsWithAlert } from "Support/utils/events"; -import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector"; +import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector"; describe('ToggleSwitch Component Tests', () => { @@ -88,7 +88,7 @@ describe('ToggleSwitch Component Tests', () => { cy.get(".tooltip-inner").invoke("hide"); openNode("components"); - openAndVerifyNode("toggleswitch1", exposedValues, verifyValue); + openAndVerifyNode("toggleswitch1", exposedValues, verifyNodeData); verifyNodes(functions, verifyfunctions); //id is pending diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js index 3802f068ed..e86f32c55e 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js @@ -10,7 +10,7 @@ import { selectColourFromColourPicker, verifyWidgetColorCss, } from "Support/utils/commonWidget"; -import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector"; +// import { verifyNodeData, openNode, verifyNodeData } from "Support/utils/inspector"; import { commonSelectors, commonWidgetSelector } from "Selectors/common"; import { commonText, diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js index a6b6a1406a..1eec4a4e55 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/inspectorHappypath.cy.js @@ -3,7 +3,7 @@ import { commonWidgetSelector } from "Selectors/common"; import { multipageSelector } from "Selectors/multipage"; import { addSupportCSAData, selectEvent } from "Support/utils/events"; import { createNewVersion } from "Support/utils/exportImport"; -import { deleteComponentFromInspector, openNode, verifyNodeData, verifyValue, verifyNodes, openAndVerifyNode } from "Support/utils/inspector"; +import { deleteComponentFromInspector, openNode, verifyNodeData, verifyNodes, openAndVerifyNode } from "Support/utils/inspector"; import { addNewPage } from "Support/utils/multipage"; import { navigateToCreateNewVersionModal } from "Support/utils/version"; import testData from "Fixtures/inspectorItems.json"; @@ -20,15 +20,15 @@ describe("Editor- Inspector", () => { cy.viewport(1800, 1800); }); - it("should verify the values of inspector", () => { + it.skip("should verify the values of inspector", () => { cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); openAndVerifyNode("globals", testData.globalsNodes, verifyNodeData); - openAndVerifyNode("currentUser", testData.currentUserNodes, verifyValue); - openAndVerifyNode("theme", testData.themeNodes, verifyValue); - openAndVerifyNode("mode", testData.modeNodes, verifyValue); - openAndVerifyNode("urlparams", testData.urlparamsNode, verifyValue); + openAndVerifyNode("currentUser", testData.currentUserNodes, verifyNodeData); + openAndVerifyNode("theme", testData.themeNodes, verifyNodeData); + openAndVerifyNode("mode", testData.modeNodes, verifyNodeData); + openAndVerifyNode("urlparams", testData.urlparamsNode, verifyNodeData); if (Cypress.env("environment") !== "Community") { const ssoUserInfoNode = '[data-cy="inspector-node-ssouserinfo"]'; @@ -39,7 +39,7 @@ describe("Editor- Inspector", () => { openNode("theme"); openNode("environment"); - verifyValue("name", "String", `"development"`); + verifyNodeData("name", "String", `"development"`); cy.get(`${inspectorNodeId} > .node-key`).should("have.text", "id"); } @@ -92,7 +92,7 @@ describe("Editor- Inspector", () => { cy.get(commonWidgetSelector.draggableWidget("button3")).click(); cy.get(commonWidgetSelector.sidebarinspector).click(); - openAndVerifyNode("variables", testData.variablesNodes, verifyValue); + openAndVerifyNode("variables", testData.variablesNodes, verifyNodeData); cy.forceClickOnCanvas() cy.wait(500) @@ -101,9 +101,9 @@ describe("Editor- Inspector", () => { // openNode("page"); - openAndVerifyNode("page", testData.testPageNodes, verifyValue); + openAndVerifyNode("page", testData.testPageNodes, verifyNodeData); openNode("variables", 1); - verifyValue("pageVar", "String", `"pageVar"`); + verifyNodeData("pageVar", "String", `"pageVar"`); openAndVerifyNode("components", testData.componentsNodes, verifyNodeData); @@ -111,10 +111,10 @@ describe("Editor- Inspector", () => { cy.get(commonWidgetSelector.draggableWidget("button1")).click(); cy.get(commonWidgetSelector.sidebarinspector).click(); - openAndVerifyNode("page", testData.pageNodes, verifyValue); + openAndVerifyNode("page", testData.pageNodes, verifyNodeData); openNode("globals"); openNode("urlparams"); - verifyValue("key", "String", `"value"`); + verifyNodeData("key", "String", `"value"`); cy.get(`[data-cy="inspector-node-key"] > .mx-1`) .realHover() diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js index 258e4ab94a..3784b6dcec 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/chainingOfQueries.cy.js @@ -104,6 +104,7 @@ describe("Chaining of queries", () => { cy.wait(1000) cy.get('[data-cy="query-tab-setup"]').click(); + cy.wait(1500); openEditorSidebar(buttonText.defaultWidgetName); selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0); cy.wait(500); @@ -122,7 +123,7 @@ describe("Chaining of queries", () => { // cy.verifyToastMessage(commonSelectors.toastMessage, "Hello World"); }); - it.skip("should verify query duplication", () => { + it("should verify query duplication", () => { const data = {}; let dsName = fake.companyName; @@ -146,6 +147,7 @@ describe("Chaining of queries", () => { chainQuery("runjs", "runpy"); addSuccessNotification("runjs"); + cy.wait(1500); openEditorSidebar(buttonText.defaultWidgetName); selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0); cy.wait(500); @@ -170,6 +172,8 @@ describe("Chaining of queries", () => { "have.text", "runjs_copy " ); + + cy.get('[data-cy="query-tab-settings"]').click(); cy.get('[data-cy="notification-on-success-toggle-switch"]').should( "have.value", "on" @@ -184,7 +188,7 @@ describe("Chaining of queries", () => { }); cy.get( `[data-cy="action-selection"] > .select-search > .react-select__control > .react-select__value-container > ` - ).should("have.text", "Run Query"); + ).should("have.text", "Run query"); cy.get('[data-cy="query-selection-field"]').should("have.text", "runpy"); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.skip.js similarity index 96% rename from cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.skip.js index b8c54b63d9..1edc529c84 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runjsHappyPath.skip.js @@ -23,7 +23,7 @@ import { resizeQueryPanel, verifypreview } from "Support/utils/dataSource"; -import { openNode, verifyValue } from "Support/utils/inspector"; +import { openNode, verifyNodeData } from "Support/utils/inspector"; describe("RunJS", () => { beforeEach(() => { @@ -42,15 +42,15 @@ describe("RunJS", () => { selectQueryFromLandingPage("runjs", "JavaScript"); addInputOnQueryField("runjs", "return true"); - query("preview"); + query("run"); verifypreview("raw", "true"); query("run"); cy.get(commonWidgetSelector.sidebarinspector).click(); cy.get(".tooltip-inner").invoke("hide"); openNode("queries"); openNode("runjs1"); - verifyValue("data", "Boolean", "true"); - verifyValue("rawData", "Boolean", "true"); + verifyNodeData("data", "Boolean", "true"); + verifyNodeData("rawData", "Boolean", "true"); cy.apiDeleteApp(); }); @@ -60,11 +60,11 @@ describe("RunJS", () => { selectQueryFromLandingPage("runjs", "JavaScript"); addInputOnQueryField("runjs", "return [page.handle,page.name]"); - query("preview"); + query("run"); verifypreview("raw", `["home","Home"]`); addInputOnQueryField("runjs", "return globals.theme"); - query("preview"); + query("run"); verifypreview("raw", `{"name":"light"}`); // addInputOnQueryField("runjs", "return globals.currentUser"); diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.skip.js similarity index 97% rename from cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.cy.js rename to cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.skip.js index ddd489f76c..2018367552 100644 --- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/queries/runpyHappyPath.skip.js @@ -24,7 +24,7 @@ import { resizeQueryPanel, verifypreview } from "Support/utils/dataSource"; -import { openNode, verifyNodeData, verifyValue } from "Support/utils/inspector"; +import { openNode, verifyNodeData } from "Support/utils/inspector"; import { addNewPage } from "Support/utils/multipage"; @@ -54,8 +54,8 @@ describe("runpy", () => { cy.get(".tooltip-inner").invoke("hide"); openNode("queries"); openNode("runpy1"); - verifyValue("data", "Boolean", "true"); - verifyValue("rawData", "Boolean", "true"); + verifyNodeData("data", "Boolean", "true"); + verifyNodeData("rawData", "Boolean", "true"); cy.apiDeleteApp(); }); @@ -76,11 +76,11 @@ actions.setPageVariable('pageVar', 'pageTest')` verifyNodeData("variables", "Object", "1 entry "); openNode("variables", 0); - verifyValue("var", "String", `"test"`); + verifyNodeData("var", "String", `"test"`); openNode("page"); openNode("variables", 1); - verifyValue("pageVar", "String", `"pageTest"`); + verifyNodeData("pageVar", "String", `"pageTest"`); addInputOnQueryField( "runpy", diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js similarity index 60% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js index 0733373ece..0f3cf9c7a5 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js @@ -3,24 +3,15 @@ import { postgreSqlSelector, airTableSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; import { airtableText } from "Texts/airTable"; import { commonSelectors } from "Selectors/common"; -import { commonText } from "Texts/common"; - -import { - fillDataSourceTextField, - selectAndAddDataSource, -} from "Support/utils/postgreSql"; - -import { - deleteDatasource, - closeDSModal, - deleteAppandDatasourceAfterExecution, -} from "Support/utils/dataSource"; - +import { closeDSModal } from "Support/utils/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; const data = {}; - data.queryName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); +const airTable_apiKey = Cypress.env("airTable_apikey"); +const airTable_baseId = Cypress.env("airtabelbaseId"); +const airTable_tableName = Cypress.env("airtable_tableName"); +const airTable_recordID = Cypress.env("airtable_recordId"); describe("Data source Airtable", () => { beforeEach(() => { @@ -54,72 +45,142 @@ describe("Data source Airtable", () => { postgreSqlText.allCloudStorage ); - selectAndAddDataSource("databases", airtableText.airtable, data.dsName); - - cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dsName}-airtable`, + "airtable", + [ + { + key: "personal_access_token", + value: `${Cypress.env("airTable_apikey")}`, + encrypted: true, + }, + ] + ); + cy.reload(); + cy.get( + dataSourceSelector.dataSourceNameButton(`cypress-${data.dsName}-airtable`) + ) + .should("be.visible") + .click(); + cy.get( + dataSourceSelector.labelFieldName(airtableText.ApiKey) + ).verifyVisibleElement("have.text", `${airtableText.ApiKey}*`); + cy.get(postgreSqlSelector.labelEncryptedText).verifyVisibleElement( "have.text", - postgreSqlText.buttonTextSave + postgreSqlText.labelEncrypted + ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).should( + "be.visible" + ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).click(); + cy.verifyRequiredFieldValidation(airtableText.ApiKey, "rgb(226, 99, 103)"); + cy.get(dataSourceSelector.textField(airtableText.ApiKey)).should( + "be.visible" + ); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy ); - cy.verifyToastMessage( - commonSelectors.toastMessage, - postgreSqlText.toastDSSaved + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation ); - deleteDatasource(`cypress-${data.dsName}-airtable`); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave) + .verifyVisibleElement("have.text", postgreSqlText.buttonTextSave) + .and("be.disabled"); + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( + "have.text", + airtableText.invalidAccessToken + ); + + cy.apiDeleteGDS(`cypress-${data.dsName}-airtable`); }); it("Should verify the functionality of AirTable connection form.", () => { - selectAndAddDataSource("databases", airtableText.airtable, data.dsName); - - fillDataSourceTextField( - airtableText.ApiKey, - airtableText.apikeyPlaceholder, - Cypress.env("airTable_apikey") - ); - cy.get(postgreSqlSelector.buttonSave).click(); - - cy.verifyToastMessage( - commonSelectors.toastMessage, - postgreSqlText.toastDSSaved - ); - cy.get(commonSelectors.globalDataSourceIcon).click(); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dsName}-airtable`, + "airtable", + [ + { + key: "personal_access_token", + value: "Invalid access token", + encrypted: true, + }, + ] + ); cy.get( - `[data-cy="cypress-${data.dsName}-airtable-button"]` - ).verifyVisibleElement("have.text", `cypress-${data.dsName}-airtable`); - deleteDatasource(`cypress-${data.dsName}-airtable`); + dataSourceSelector.dataSourceNameButton(`cypress-${data.dsName}-airtable`) + ) + .should("be.visible") + .click(); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(dataSourceSelector.connectionFailedText, { + timeout: 10000, + }).should("have.text", postgreSqlText.couldNotConnect); + + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( + "have.text", + airtableText.invalidAccessToken + ); + cy.reload(); + cy.apiUpdateGDS({ + name: `cypress-${data.dsName}-airtable`, + options: [ + { + key: "personal_access_token", + value: `${Cypress.env("airTable_apikey")}`, + encrypted: true, + }, + ], + }); + cy.get( + dataSourceSelector.dataSourceNameButton(`cypress-${data.dsName}-airtable`) + ) + .should("be.visible") + .click(); + + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + + cy.apiDeleteGDS(`cypress-${data.dsName}-airtable`); }); it("Should able to run the query with valid conection", () => { - const airTable_apiKey = Cypress.env("airTable_apikey"); - const airTable_baseId = Cypress.env("airtabelbaseId"); - const airTable_tableName = Cypress.env("airtable_tableName"); - const airTable_recordID = Cypress.env("airtable_recordId"); - - selectAndAddDataSource("databases", airtableText.airtable, data.dsName); - - fillDataSourceTextField( - airtableText.ApiKey, - airtableText.apikeyPlaceholder, - airTable_apiKey + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dsName}-airtable`, + "airtable", + [ + { + key: "personal_access_token", + value: `${airTable_apiKey}`, + encrypted: true, + }, + ] ); - - cy.wait(1000); - cy.get(postgreSqlSelector.buttonSave).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - postgreSqlText.toastDSSaved - ); - - cy.get(commonSelectors.globalDataSourceIcon).click(); - cy.get( - `[data-cy="cypress-${data.dsName}-airtable-button"]` - ).verifyVisibleElement("have.text", `cypress-${data.dsName}-airtable`); - cy.get(commonSelectors.dashboardIcon).click(); - cy.get(commonSelectors.appCreateButton).click(); - cy.get(commonSelectors.appNameInput).click().type(data.dsName); - cy.get(commonSelectors.createAppButton).click(); - cy.skipWalkthrough(); + cy.apiCreateApp(`${data.dsName}-airtable-app`); + cy.openApp(); cy.get('[data-cy="show-ds-popover-button"]').click(); cy.get(".css-4e90k9").type(`${data.dsName}`); @@ -280,10 +341,9 @@ describe("Data source Airtable", () => { commonSelectors.toastMessage, `Query (${data.queryName}) completed.` ); - deleteAppandDatasourceAfterExecution( - data.dsName, - `cypress-${data.dsName}-airtable` - ); + + cy.apiDeleteApp(`${data.dsName}-airtable-app`); + cy.apiDeleteGDS(`cypress-${data.dsName}-airtable`); }); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js similarity index 79% rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js index 24a39a3acc..2bbe0787e7 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js @@ -66,10 +66,9 @@ describe("Data source BigQuery", () => { `cypress-${data.dataSourceName}-bigquery` ); - cy.get('[data-cy="label-private-key"]').verifyVisibleElement( - "have.text", - firestoreText.labelPrivateKey - ); + cy.get( + dataSourceSelector.labelFieldName(firestoreText.labelPrivateKey) + ).verifyVisibleElement("have.text", "Private key*"); cy.get(".datasource-edit-btn").should("be.visible"); cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( "have.text", @@ -98,7 +97,7 @@ describe("Data source BigQuery", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", bigqueryText.errorInvalidEmailId ); @@ -110,38 +109,30 @@ describe("Data source BigQuery", () => { }); it("Should verify the functionality of BigQuery connection form.", () => { - selectAndAddDataSource( - "databases", - bigqueryText.bigQuery, - data.dataSourceName - ); - - fillDataSourceTextField( - firestoreText.privateKey, - bigqueryText.placehlderPrivateKey, - `${JSON.stringify(Cypress.env("bigquery_pvt_key"))}`, - "contain", - { parseSpecialCharSequences: false, delay: 0 } + cy.get(commonSelectors.globalDataSourceIcon).click(); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-bigquery`, + "bigquery", + [ + { + key: "private_key", + value: `${JSON.stringify(Cypress.env("bigquery_pvt_key"))}`, + encrypted: true, + }, + ] ); + cy.get( + dataSourceSelector.dataSourceNameButton( + `cypress-${data.dataSourceName}-bigquery` + ) + ) + .should("be.visible") + .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); cy.get(postgreSqlSelector.textConnectionVerified, { timeout: 10000, }).should("have.text", postgreSqlText.labelConnectionVerified); - cy.get(postgreSqlSelector.buttonSave).click(); - - cy.verifyToastMessage( - commonSelectors.toastMessage, - postgreSqlText.toastDSSaved - ); - - cy.get(commonSelectors.globalDataSourceIcon).click(); - cy.get( - `[data-cy="cypress-${data.dataSourceName}-bigquery-button"]` - ).verifyVisibleElement( - "have.text", - `cypress-${data.dataSourceName}-bigquery` - ); - - deleteDatasource(`cypress-${data.dataSourceName}-bigquery`); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-bigquery`); }); }); 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 bb5923ec7b..e450e52fc6 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 @@ -101,7 +101,7 @@ describe("Data sources", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", "Invalid URL" ); 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 8e4a17d173..06bfe675fd 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 @@ -127,7 +127,7 @@ describe("Data sources", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", "Invalid URL" ); 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 5ff912d2d8..3585fc9c4f 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 @@ -115,7 +115,7 @@ describe("Data source DynamoDB", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", dynamoDbText.errorMissingRegion ); 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 88627284ad..8faca425a0 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 @@ -122,7 +122,7 @@ describe("Data source Elasticsearch", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", elasticsearchText.errorConnectionRefused ); 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 674501b2db..31b6099d4c 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 @@ -94,7 +94,7 @@ describe("Data source Firestore", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", firestoreText.errorGcpKeyCouldNotBeParsed ); 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 24dc92359c..26db831cff 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 @@ -118,7 +118,7 @@ describe("Data sources", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", "Invalid URL" ); 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 58c8c30705..020ca4a40a 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 @@ -125,7 +125,7 @@ describe("Data sources", () => { "have.text", postgreSqlText.buttonTextSave ); - // cy.get('[data-cy="connection-alert-text"]').should("be.visible") + // cy.get(dataSourceSelector.connectionAlertText).should("be.visible") deleteDatasource(`cypress-${data.dataSourceName}-mariadb`); }); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js index 5729ea18c8..1da8b5c6d7 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js @@ -129,7 +129,7 @@ describe("Data source MongoDB", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", mongoDbText.errorConnectionRefused ); @@ -164,7 +164,7 @@ describe("Data source MongoDB", () => { }).verifyVisibleElement("have.text", postgreSqlText.couldNotConnect, { timeout: 95000, }); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", "Cannot read properties of null (reading '2')" ); 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 2da4902edd..b86ca7cb17 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 @@ -27,7 +27,7 @@ describe("Data sources", () => { .replaceAll("[^A-Za-z]", ""); }); - it.skip("Should verify elements on connection form", () => { + it("Should verify elements on connection form with validation", () => { cy.log(process.env.NODE_ENV); cy.log(postgreSqlText.allDatabase()); cy.get(commonSelectors.globalDataSourceIcon).click(); @@ -81,30 +81,147 @@ describe("Data sources", () => { `cypress-${data.dataSourceName}-postgresql` ); - cy.get(postgreSqlSelector.labelHost).verifyVisibleElement( + cy.get( + dataSourceSelector.dropdownLabel(postgreSqlText.labelConnectionType) + ).verifyVisibleElement("have.text", postgreSqlText.labelConnectionType); + cy.get(dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType)) + .should("be.visible") + .click(); + cy.contains( + `[id*="react-select-"]`, + postgreSqlText.connectionStringOption + ).click(); + + cy.get( + dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType) + ).should("be.visible"); + cy.get( + dataSourceSelector.labelFieldName(postgreSqlText.connectionStringOption) + ).verifyVisibleElement( "have.text", - postgreSqlText.labelHost + `${postgreSqlText.connectionStringOption}*` ); - cy.get(postgreSqlSelector.labelPort).verifyVisibleElement( + cy.get(postgreSqlSelector.labelEncryptedText).verifyVisibleElement( "have.text", - postgreSqlText.labelPort + postgreSqlText.labelEncrypted ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).should( + "be.visible" + ); + cy.get(dataSourceSelector.button(postgreSqlText.editButtonText)).click(); + cy.verifyRequiredFieldValidation( + postgreSqlText.connectionStringOption, + "rgb(226, 99, 103)" + ); + cy.get( + dataSourceSelector.textField(postgreSqlText.connectionStringOption) + ).should("be.visible"); + cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement( + "have.text", + postgreSqlText.whiteListIpText + ); + cy.get(postgreSqlSelector.buttonCopyIp).verifyVisibleElement( + "have.text", + postgreSqlText.textCopy + ); + + cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement( + "have.text", + postgreSqlText.readDocumentation + ); + cy.get(postgreSqlSelector.buttonTestConnection) + .verifyVisibleElement( + "have.text", + postgreSqlText.buttonTextTestConnection + ) + .click(); + cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement( + "have.text", + postgreSqlText.couldNotConnect + ); + cy.get(postgreSqlSelector.buttonSave) + .verifyVisibleElement("have.text", postgreSqlText.buttonTextSave) + .and("be.disabled"); + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( + "have.text", + postgreSqlText.unableAcquireConnectionAlertText + ); + + cy.get(dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType)) + .should("be.visible") + .click(); + cy.contains( + `[id*="react-select-"]`, + postgreSqlText.manualConnectionOption + ).click(); + + cy.get( + dataSourceSelector.dropdownField(postgreSqlText.labelConnectionType) + ).should("be.visible"); + + const requiredFields = [ + postgreSqlText.labelHost, + postgreSqlText.labelPort, + postgreSqlText.labelUserName, + postgreSqlText.labelPassword, + ]; + const sections = [ + postgreSqlText.labelHost, + postgreSqlText.labelPort, + postgreSqlText.labelDbName, + postgreSqlText.labelUserName, + postgreSqlText.labelPassword, + postgreSqlText.labelConnectionOptions, + ]; + sections.forEach((section) => { + if (section === postgreSqlText.labelConnectionOptions) { + cy.get(dataSourceSelector.keyInputField(section, 0)).should( + "be.visible" + ); + cy.get(dataSourceSelector.valueInputField(section, 0)).should( + "be.visible" + ); + cy.get(dataSourceSelector.deleteButton(section, 0)).should( + "be.visible" + ); + cy.get(dataSourceSelector.addMoreButton(section)).should("be.visible"); + } else if (requiredFields.includes(section)) { + cy.get(dataSourceSelector.labelFieldName(section)).verifyVisibleElement( + "have.text", + `${section}*` + ); + cy.get(dataSourceSelector.textField(section)).should("be.visible"); + if (section === postgreSqlText.labelPassword) { + cy.get( + dataSourceSelector.button(postgreSqlText.editButtonText) + ).click(); + cy.verifyRequiredFieldValidation(section, "rgb(215, 45, 57)"); + } else { + cy.get(dataSourceSelector.textField(section)).click(); + cy.get(commonSelectors.textField(section)).should( + "have.css", + "border-color", + "rgba(0, 0, 0, 0)" + ); + cy.get(dataSourceSelector.textField(section)) + .type("123") + .clear() + .blur(); + cy.verifyRequiredFieldValidation(section, "rgb(215, 45, 57)"); + } + } else { + cy.get(dataSourceSelector.labelFieldName(section)).verifyVisibleElement( + "have.text", + section + ); + cy.get(dataSourceSelector.textField(section)).should("be.visible"); + } + }); cy.get(postgreSqlSelector.labelSsl).verifyVisibleElement( "have.text", postgreSqlText.labelSSL ); - cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement( - "have.text", - postgreSqlText.labelDbName - ); - cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement( - "have.text", - postgreSqlText.labelUserName - ); - cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement( - "have.text", - postgreSqlText.labelPassword - ); + cy.get(postgreSqlSelector.sslToggleInput).should("be.visible"); cy.get(postgreSqlSelector.labelSSLCertificate).verifyVisibleElement( "have.text", postgreSqlText.sslCertificate @@ -132,72 +249,85 @@ describe("Data sources", () => { "have.text", postgreSqlText.couldNotConnect ); - cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement( + cy.get(postgreSqlSelector.buttonSave) + .verifyVisibleElement("have.text", postgreSqlText.buttonTextSave) + .and("be.disabled"); + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", - postgreSqlText.buttonTextSave + "connect ECONNREFUSED 127.0.0.1:5432" ); - cy.get('[data-cy="connection-alert-text"]').should("be.visible"); - deleteDatasource(`cypress-${data.dataSourceName}-postgresql`); + + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-postgresql`); }); - it.skip("Should verify the functionality of PostgreSQL connection form.", () => { - selectAndAddDataSource( - "databases", - postgreSqlText.postgreSQL, - data.dataSourceName + it("Should verify the functionality of PostgreSQL connection form.", () => { + cy.get(commonSelectors.globalDataSourceIcon).click(); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-manual-pgsql`, + "postgresql", + [ + { key: "connection_type", value: "manual", encrypted: false }, + { key: "host", value: `${Cypress.env("pg_host")}`, encrypted: false }, + { key: "port", value: 5432, encrypted: false }, + { key: "ssl_enabled", value: false, encrypted: false }, + { key: "database", value: "postgres", encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + { + key: "username", + value: `${Cypress.env("pg_user")}`, + encrypted: false, + }, + { + key: "password", + value: `${Cypress.env("pg_password")}`, + encrypted: true, + }, + { key: "ca_cert", value: null, encrypted: true }, + { key: "client_key", value: null, encrypted: true }, + { key: "client_cert", value: null, encrypted: true }, + { key: "root_cert", value: null, encrypted: true }, + { key: "connection_string", value: null, encrypted: true }, + ] ); - - fillDataSourceTextField( - postgreSqlText.labelHost, - postgreSqlText.placeholderEnterHost, - Cypress.env("pg_host") - ); - fillDataSourceTextField( - postgreSqlText.labelPort, - postgreSqlText.placeholderEnterPort, - "5432" - ); - cy.get('[data-cy="-toggle-input"]').then(($el) => { - if ($el.is(":checked")) { - cy.get('[data-cy="-toggle-input"]').uncheck(); - } - }); - fillDataSourceTextField( - postgreSqlText.labelDbName, - postgreSqlText.placeholderNameOfDB, - "postgres" - ); - fillDataSourceTextField( - postgreSqlText.labelUserName, - postgreSqlText.placeholderEnterUserName, - "postgres" - ); - fillDataSourceTextField( - postgreSqlText.labelPassword, - "**************", - Cypress.env("pg_password") - ); - + cy.get( + dataSourceSelector.dataSourceNameButton( + `cypress-${data.dataSourceName}-manual-pgsql` + ) + ) + .should("be.visible") + .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); cy.get(postgreSqlSelector.textConnectionVerified, { timeout: 10000, }).should("have.text", postgreSqlText.labelConnectionVerified); - cy.get(postgreSqlSelector.buttonSave).click(); - - cy.verifyToastMessage( - commonSelectors.toastMessage, - postgreSqlText.toastDSSaved + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-manual-pgsql`); + cy.reload(); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-string-pgsql`, + "postgresql", + [ + { key: "connection_type", value: "string", encrypted: false }, + { + key: "connection_string", + value: `${Cypress.env("pg_string")}`, + encrypted: true, + }, + ] ); - - cy.get(commonSelectors.globalDataSourceIcon).click(); cy.get( - `[data-cy="cypress-${data.dataSourceName}-postgresql-button"]` - ).verifyVisibleElement( - "have.text", - `cypress-${data.dataSourceName}-postgresql` - ); - - deleteDatasource(`cypress-${data.dataSourceName}-postgresql`); + dataSourceSelector.dataSourceNameButton( + `cypress-${data.dataSourceName}-string-pgsql` + ) + ) + .should("be.visible") + .click(); + cy.get(postgreSqlSelector.buttonTestConnection).click(); + cy.get(postgreSqlSelector.textConnectionVerified, { + timeout: 10000, + }).should("have.text", postgreSqlText.labelConnectionVerified); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-string-pgsql`); }); it.skip("Should verify elements of the Query section.", () => { 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 ddceea1b52..7e4ed90b8d 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 @@ -3,7 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql"; import { postgreSqlText } from "Texts/postgreSql"; import { redisText } from "Texts/redis"; import { commonSelectors } from "Selectors/common"; -import { commonText } from "Texts/common"; +import { dataSourceSelector } from "Selectors/dataSource"; import { fillDataSourceTextField, @@ -96,7 +96,7 @@ describe("Data source Redis", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", redisText.errorMaxRetries ); @@ -137,7 +137,7 @@ describe("Data source Redis", () => { ); cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", redisText.errorInvalidUserOrPassword ); @@ -152,7 +152,7 @@ describe("Data source Redis", () => { "108299" ); cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", redisText.errorPort ); @@ -170,7 +170,7 @@ describe("Data source Redis", () => { ); cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", redisText.errorInvalidUserOrPassword ); @@ -187,7 +187,7 @@ describe("Data source Redis", () => { "redis" ); cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", redisText.errorInvalidUserOrPassword ); 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 825439e687..09559e2ba7 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 @@ -329,9 +329,9 @@ describe("Data source Rest API", () => { ); cy.contains("Save").click(); cy.verifyToastMessage(commonSelectors.toastMessage, "Data Source Saved"); - deleteDatasource(`cypress-${data.dataSourceName}-restapi`); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-restapi`); }); - it("Should verify basic connection for Rest API", () => { + it("Should verify connection response for all methods", () => { cy.apiCreateGDS( `${Cypress.env("server_host")}/api/data-sources`, `cypress-${data.dataSourceName}-restapi`, @@ -367,88 +367,388 @@ describe("Data source Rest API", () => { ); cy.reload(); - cy.apiCreateApp(`${fake.companyName}-restAPI-App`); + cy.apiCreateApp(`${fake.companyName}-restAPI-CURD-App`); cy.openApp(); - createAndRunRestAPIQuery( - "get_restapi", - `cypress-${data.dataSourceName}-restapi`, - "GET", - "/api/users" - ); - createAndRunRestAPIQuery( - "post_restapi", - `cypress-${data.dataSourceName}-restapi`, - "POST", - "", - [["Content-Type", "application/json"]], - [], - { - price: 200, - name: "Violin", - }, - true, - "/api/users" - ); + createAndRunRestAPIQuery({ + queryName: "get_beeceptor_data", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "GET", + urlSuffix: "/api/users", + run: true, + }); + createAndRunRestAPIQuery({ + queryName: "post_restapi", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "POST", + headersList: [["Content-Type", "application/json"]], + rawBody: '{"price": 200,"name": "Violin"}', + urlSuffix: "/api/users", + expectedResponseShape: { price: 200, name: "Violin", id: true }, + }); cy.readFile("cypress/fixtures/restAPI/storedId.json").then( (postResponseID) => { const id1 = postResponseID.id; - createAndRunRestAPIQuery( - "put_restapi_id", - `cypress-${data.dataSourceName}-restapi`, - "PUT", - "", - [["Content-Type", "application/json"]], - [], - { - price: 500, - name: "Guitar", - }, - true, - `/api/users/${id1}` - ); - } - ); - cy.readFile("cypress/fixtures/restAPI/storedId.json").then( - (putResponseID) => { - const id2 = putResponseID.id; - createAndRunRestAPIQuery( - "patch_restapi_id", - `cypress-${data.dataSourceName}-restapi`, - "PATCH", - "", - [["Content-Type", "application/json"]], - [], - { price: 999 }, - true, - `/api/users/${id2}` - ); - } - ); - cy.readFile("cypress/fixtures/restAPI/storedId.json").then( - (patchResponseID) => { - const id3 = patchResponseID.id; - createAndRunRestAPIQuery( - "get_restapi_id", - `cypress-${data.dataSourceName}-restapi`, - "GET", - "", - [], - [], - true, - `/api/users/${id3}` - ); - createAndRunRestAPIQuery( - "delete_restapi_id", - `cypress-${data.dataSourceName}-restapi`, - "DELETE", - "", - [], - [], - true, - `/api/users/${id3}` - ); + createAndRunRestAPIQuery({ + queryName: "put_restapi_id", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "PUT", + headersList: [["Content-Type", "application/json"]], + rawBody: '{"price": 500,"name": "Guitar"}', + urlSuffix: `/api/users/${id1}`, + expectedResponseShape: { price: 500, name: "Guitar", id: id1 }, + }); + createAndRunRestAPIQuery({ + queryName: "patch_restapi_id", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "PATCH", + headersList: [["Content-Type", "application/json"]], + rawBody: '{"price": 999 }', + urlSuffix: `/api/users/${id1}`, + run: true, + expectedResponseShape: { price: 999, id: id1 }, + }); + createAndRunRestAPIQuery({ + queryName: "get_restapi_id", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "GET", + urlSuffix: `/api/users/${id1}`, + run: true, + expectedResponseShape: { price: 999, name: "Guitar", id: id1 }, + }); + createAndRunRestAPIQuery({ + queryName: "delete_restapi_id", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "DELETE", + urlSuffix: `/api/users/${id1}`, + run: true, + expectedResponseShape: { success: true }, + }); } ); + cy.apiDeleteApp(`${fake.companyName}-restAPI-CURD-App`); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-restapi`); + }); + it("Should verify response for basic authentication type connection", () => { + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-restapi`, + "restapi", + [ + { key: "url", value: "https://httpbin.org" }, + { key: "auth_type", value: "basic" }, + { key: "grant_type", value: "authorization_code" }, + { key: "add_token_to", value: "header" }, + { key: "header_prefix", value: "Bearer " }, + { key: "access_token_url", value: "" }, + { key: "client_id", value: "" }, + { + key: "client_secret", + encrypted: true, + credential_id: "b044a293-82b4-4381-84fd-d173c86a6a0c", + }, + { key: "audience", value: "" }, + { key: "scopes", value: "read, write" }, + { key: "username", value: "user", encrypted: false }, + { key: "password", value: "pass", encrypted: true }, + { + key: "bearer_token", + encrypted: true, + credential_id: "21caf3cb-dbde-43c7-9f42-77feffb63062", + }, + { key: "auth_url", value: "" }, + { key: "client_auth", value: "header" }, + { key: "headers", value: [["", ""]] }, + { key: "custom_query_params", value: [["", ""]], encrypted: false }, + { key: "custom_auth_params", value: [["", ""]] }, + { + key: "access_token_custom_headers", + value: [["", ""]], + encrypted: false, + }, + { key: "multiple_auth_enabled", value: false, encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + { key: "retry_network_errors", value: true, encrypted: false }, + { key: "url_parameters", value: [["", ""]], encrypted: false }, + { key: "tokenData", encrypted: false }, + ] + ); + cy.reload(); + cy.intercept("GET", "/api/library_apps").as("appLibrary"); + cy.apiCreateApp(`${fake.companyName}-restAPI-Basic-App`); + createAndRunRestAPIQuery({ + queryName: "get_basic_auth_valid", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "GET", + urlSuffix: "/basic-auth/user/pass", + expectedResponseShape: { authenticated: true, user: "user" }, + }); + createAndRunRestAPIQuery({ + queryName: "get_basic_auth_invalid", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "GET", + urlSuffix: "/basic-auth/invaliduser/invalidpass", + }); + cy.apiDeleteApp(`${fake.companyName}-restAPI-Basic-App`); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-restapi`); + }); + it("Should verify response for bearer authentication type connection", () => { + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-restapi`, + "restapi", + [ + { key: "url", value: "https://httpbin.org" }, + { key: "auth_type", value: "bearer" }, + { key: "grant_type", value: "authorization_code" }, + { key: "add_token_to", value: "header" }, + { key: "header_prefix", value: "Bearer " }, + { key: "access_token_url", value: "" }, + { key: "client_id", value: "" }, + { + key: "client_secret", + encrypted: true, + credential_id: "b044a293-82b4-4381-84fd-d173c86a6a0c", + }, + { key: "audience", value: "" }, + { key: "scopes", value: "read, write" }, + { key: "username", value: "", encrypted: false }, + { key: "password", value: "", encrypted: true }, + { + key: "bearer_token", + value: "my-token-123", + encrypted: true, + }, + { key: "auth_url", value: "" }, + { key: "client_auth", value: "header" }, + { key: "headers", value: [["", ""]] }, + { key: "custom_query_params", value: [["", ""]], encrypted: false }, + { key: "custom_auth_params", value: [["", ""]] }, + { + key: "access_token_custom_headers", + value: [["", ""]], + encrypted: false, + }, + { key: "multiple_auth_enabled", value: false, encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + { key: "retry_network_errors", value: true, encrypted: false }, + { key: "url_parameters", value: [["", ""]], encrypted: false }, + { key: "tokenData", encrypted: false }, + ] + ); + cy.reload(); + cy.intercept("GET", "/api/library_apps").as("appLibrary"); + cy.apiCreateApp(`${fake.companyName}-restAPI-Bearer-App`); + cy.openApp(); + createAndRunRestAPIQuery({ + queryName: "get_bearer_auth_valid", + dsName: `cypress-${data.dataSourceName}-restapi`, + method: "GET", + urlSuffix: "/bearer", + expectedResponseShape: { authenticated: true, token: "my-token-123" }, + }); + cy.apiDeleteApp(`${fake.companyName}-restAPI-Bearer-App`); + cy.intercept("GET", "api/data_sources?**").as("datasource"); + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-restapi-invalid`, + "restapi", + [ + { key: "url", value: "https://httpbin.org" }, + { key: "auth_type", value: "bearer" }, + { key: "grant_type", value: "authorization_code" }, + { key: "add_token_to", value: "header" }, + { key: "header_prefix", value: "Bearer " }, + { key: "access_token_url", value: "" }, + { key: "client_id", value: "" }, + { + key: "client_secret", + encrypted: true, + credential_id: "b044a293-82b4-4381-84fd-d173c86a6a0c", + }, + { key: "audience", value: "" }, + { key: "scopes", value: "read, write" }, + { key: "username", value: "", encrypted: false }, + { key: "password", value: "", encrypted: true }, + { + key: "bearer_token", + value: "", + encrypted: true, + }, + { key: "auth_url", value: "" }, + { key: "client_auth", value: "header" }, + { key: "headers", value: [["", ""]] }, + { key: "custom_query_params", value: [["", ""]], encrypted: false }, + { key: "custom_auth_params", value: [["", ""]] }, + { + key: "access_token_custom_headers", + value: [["", ""]], + encrypted: false, + }, + { key: "multiple_auth_enabled", value: false, encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + { key: "retry_network_errors", value: true, encrypted: false }, + { key: "url_parameters", value: [["", ""]], encrypted: false }, + { key: "tokenData", encrypted: false }, + ] + ); + cy.apiCreateApp(`${fake.companyName}-restAPI-Bearer-invalid`); + cy.openApp(); + createAndRunRestAPIQuery({ + queryName: "get_bearer_auth_invalid", + dsName: `cypress-${data.dataSourceName}-restapi-invalid`, + method: "GET", + urlSuffix: "/bearer", + }); + cy.apiDeleteApp(`${fake.companyName}-restAPI-Bearer-invalid`); + cy.apiDeleteGDS(`cypress-${data.dataSourceName}-restapi`); + }); + it.skip("Should verify response for authentication code grant type connection", () => { + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + `cypress-${data.dataSourceName}-restapi`, + "restapi", + [ + { + key: "url", + value: "https://dev-6lj2hoxdz5fg3m57.uk.auth0.com/api/v2/users", + }, + { key: "auth_type", value: "oauth2" }, + { key: "grant_type", value: "client_credentials" }, + { key: "add_token_to", value: "header" }, + { key: "header_prefix", value: "Bearer " }, + { + key: "access_token_url", + value: "https://dev-6lj2hoxdz5fg3m57.uk.auth0.com/oauth/token", + }, + { key: "client_id", value: "JBDuuLU9vaSTP6Do7zYSkw0GvVgWhfyZ" }, + { + key: "client_secret", + encrypted: true, + credential_id: "a6d26607-4d09-42a2-8bc0-e5c185c7c2f7", + }, + { + key: "audience", + value: "https://dev-6lj2hoxdz5fg3m57.uk.auth0.com/api/v2/", + }, + { key: "scopes", value: "" }, + { key: "username", value: "", encrypted: false }, + { + key: "password", + encrypted: true, + credential_id: "4502a906-b512-447a-a128-39f67e9778d2", + }, + { + key: "bearer_token", + encrypted: true, + credential_id: "c94262c7-d2c5-4d7f-96f8-657689f2b1f0", + }, + { key: "auth_url", value: "" }, + { key: "client_auth", value: "header" }, + { key: "headers", value: [["", ""]] }, + { key: "custom_query_params", value: [["", ""]], encrypted: false }, + { key: "custom_auth_params", value: [["", ""]] }, + { + key: "access_token_custom_headers", + value: [["", ""]], + encrypted: false, + }, + { key: "multiple_auth_enabled", value: false, encrypted: false }, + { key: "ssl_certificate", value: "none", encrypted: false }, + { key: "retry_network_errors", value: true, encrypted: false }, + ] + ); + cy.reload(); + cy.intercept("GET", "/api/library_apps").as("appLibrary"); + cy.apiCreateApp(`${fake.companyName}-client-Grant-RestAPI`); + }); + it("Should verify response for content-type", () => { + cy.apiCreateApp(`${fake.companyName}-restAPI-Content-App`); + createAndRunRestAPIQuery({ + queryName: "post_json", + dsName: "restapidefault", + method: "POST", + url: "https://jsonplaceholder.typicode.com/posts", + headersList: [["Content-Type", "application/json"]], + rawBody: '{"title": "foo","body": "bar","userId": 1}', + run: true, + urlSuffix: "", + expectedResponseShape: { id: true, title: "foo", body: "bar", userId: 1 }, + }); + createAndRunRestAPIQuery({ + queryName: "post_raw_text", + dsName: "restapidefault", + method: "POST", + url: "https://httpbin.org/post", + headersList: [["Content-Type", "text/plain"]], + rawBody: "This is plain text content", + jsonBody: null, + run: true, + expectedResponseShape: { data: "This is plain text content" }, + }); + createAndRunRestAPIQuery({ + queryName: "post_form_urlencoded", + dsName: "restapidefault", + method: "POST", + url: "https://httpbin.org/post", + headersList: [["Content-Type", "application/x-www-form-urlencoded"]], + bodyList: [ + ["name", "Jane"], + ["age", "30"], + ], + expectedResponseShape: { + "form.name": "Jane", + "form.age": "30", + }, + }); + createAndRunRestAPIQuery({ + queryName: "post_xml_soap", + dsName: "restapidefault", + method: "POST", + url: "http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL", + headersList: [["Content-Type", "text/xml; charset=utf-8"]], + rawBody: ` + + + + + + `, + jsonBody: null, + bodyList: [], + cookiesList: [["session", "abc123"]], + paramsList: [["lang", "en"]], + run: true, + shouldSucceed: true, + expectedResponseShape: {}, + }); + // createAndRunRestAPIQuery({ + // queryName: "post_text_csv", + // dsName: "restapidefault", + // method: "POST", + // url: `https://tejasvi.free.beeceptor.com/csv-upload`, + // headersList: [["Content-Type", "text/csv"]], + // rawBody: + // "id,name,email\n1,Alice,alice@example.com\n2,Bob,bob@example.com", + // expectedResponseShape: { + // data: '{\n "status": "ok",\n "message": "File uploaded successfully",\n "body": id,name,email\n1,Alice,alice@example.com\n2,Bob,bob@example.com\n}', + // }, + // }); + // const filename = "tooljet.png"; + + // createAndRunRestAPIQuery({ + // queryName: "upload_image", + // dsName: "restapidefault", + // method: "POST", + // url: `https://tejasvi.free.beeceptor.commultipart-upload`, + // headersList: [["Content-Type", "multipart/form-data"]], + // bodyList: [ + // ["Image_File", "fixture:Image/tooljet.png"], + // ["filename", filename], + // ], + // expectedResponseShape: { + // filename: filename, + // }, + // }); }); }); 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 73ccc703c3..79f2534f9e 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 @@ -120,7 +120,7 @@ describe("Data sources AWS S3", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", s3Text.alertRegionIsMissing ); @@ -144,7 +144,7 @@ describe("Data sources AWS S3", () => { ); cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", s3Text.alertRegionIsMissing ); @@ -170,7 +170,7 @@ describe("Data sources AWS S3", () => { .click(); cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", s3Text.alertInvalidUrl ); @@ -188,7 +188,7 @@ describe("Data sources AWS S3", () => { ); cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", s3Text.accessKeyError ); @@ -207,7 +207,7 @@ describe("Data sources AWS S3", () => { cy.get(postgreSqlSelector.buttonTestConnection).click(); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", s3Text.sinatureError ); 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 506fe5d660..15d716ae8e 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 @@ -105,7 +105,7 @@ describe("Data source SMTP", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", "Invalid credentials" ); 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 7c1fb5b588..e8419657bd 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 @@ -128,7 +128,7 @@ describe("Data sources", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", "Invalid account. The specified value must be a valid subdomain string." ); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js index 954fb659b5..1ac953a42b 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js @@ -132,7 +132,7 @@ describe("Data sources", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').verifyVisibleElement( + cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", "Failed to connect to localhost:1433 - Could not connect (sequence)" ); 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 4b4fe20e62..9e480da66c 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 @@ -96,7 +96,7 @@ describe("Data sources", () => { "have.text", postgreSqlText.buttonTextSave ); - cy.get('[data-cy="connection-alert-text"]').should( + cy.get(dataSourceSelector.connectionAlertText).should( "have.text", "Ensure that apiKey is set" ); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js index c0f6064564..3432744fc3 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/version.cy.js @@ -114,17 +114,30 @@ describe("App Version", () => { cy.wait(3000); // cy.reload(); - cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible", { timeout: 10000 }); + cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible", { + timeout: 10000, + }); // Preview and release verification cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.url().should("include", "/home?version=v2"); - cy.openApp("", Cypress.env("workspaceId"), Cypress.env("appId"), commonWidgetSelector.draggableWidget("text1")); + cy.openApp( + "", + Cypress.env("workspaceId"), + Cypress.env("appId"), + commonWidgetSelector.draggableWidget("text1") + ); releasedVersionAndVerify("v2"); }); it("should verify version management with components and queries", () => { // Initial setup with component and datasource + cy.apiCreateGDS( + `${Cypress.env("server_host")}/api/data-sources`, + data.datasourceName, + "restapi", + [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] + ); cy.apiAddComponentToApp( data.appName, "text1", @@ -134,19 +147,15 @@ describe("App Version", () => { ); cy.waitForAutoSave(); - cy.apiCreateGDS( - `${Cypress.env("server_host")}/api/data-sources`, - data.datasourceName, - "restapi", - [{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }] - ); createRestAPIQuery(data.query1, data.datasourceName, "", "", "/1", true); // Version v2 creation and verification navigateToCreateNewVersionModal("v1"); createNewVersion(["v2"], "v1"); - cy.get(commonWidgetSelector.draggableWidget("text1")) - .verifyVisibleElement("have.text", "Leanne Graham"); + cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( + "have.text", + "Leanne Graham" + ); cy.get(`[data-cy="list-query-${data.query1}"]`).should("be.visible"); // Modify v2 with new components and queries @@ -170,67 +179,79 @@ describe("App Version", () => { create: { version: "v3", from: "v2" }, verify: { component: { selector: "textInput", value: "Ervin Howell" }, - query: data.query2 - } + query: data.query2, + }, }, { create: { version: "v4", from: "v1" }, verify: { component: { selector: "text1", text: "Leanne Graham" }, - query: data.query1 - } + query: data.query1, + }, }, { create: { version: "v5", from: "v3" }, verify: { component: { selector: "textInput", value: "Ervin Howell" }, - query: data.query2 - } - } + query: data.query2, + }, + }, ]; - versionChecks.forEach(check => { + versionChecks.forEach((check) => { navigateToCreateNewVersionModal(check.create.from); createNewVersion([check.create.version], check.create.from); if (check.verify.component.value) { - cy.get(commonWidgetSelector.draggableWidget(check.verify.component.selector)) - .verifyVisibleElement("have.value", check.verify.component.value); + cy.get( + commonWidgetSelector.draggableWidget(check.verify.component.selector) + ).verifyVisibleElement("have.value", check.verify.component.value); } else { - cy.get(commonWidgetSelector.draggableWidget(check.verify.component.selector)) - .verifyVisibleElement("have.text", check.verify.component.text); + cy.get( + commonWidgetSelector.draggableWidget(check.verify.component.selector) + ).verifyVisibleElement("have.text", check.verify.component.text); } - cy.get(`[data-cy="list-query-${check.verify.query}"]`).should("be.visible"); + cy.get(`[data-cy="list-query-${check.verify.query}"]`).should( + "be.visible" + ); }); // Release and version state verification releasedVersionAndVerify("v5"); - cy.get(appVersionSelectors.currentVersionField("v5")) - .should("have.class", "color-light-green"); + cy.get(appVersionSelectors.currentVersionField("v5")).should( + "have.class", + "color-light-green" + ); // Version switching and component verification cy.get(appVersionSelectors.currentVersionField("v5")).click(); cy.contains(`[id*="react-select-"]`, "v4").click(); - cy.get(appVersionSelectors.currentVersionField("v4")) - .should("not.have.class", "color-light-green"); - cy.get(commonWidgetSelector.draggableWidget("text1")) - .verifyVisibleElement("have.text", "Leanne Graham"); + cy.get(appVersionSelectors.currentVersionField("v4")).should( + "not.have.class", + "color-light-green" + ); + cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( + "have.text", + "Leanne Graham" + ); cy.get(`[data-cy="list-query-${data.query1}"]`).should("be.visible"); // Preview and version switching verification cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.url().should("include", "/home?version=v4"); - cy.get(commonWidgetSelector.draggableWidget("text1")) - .verifyVisibleElement("have.text", "Leanne Graham"); + cy.get(commonWidgetSelector.draggableWidget("text1")).verifyVisibleElement( + "have.text", + "Leanne Graham" + ); cy.get('[data-cy="preview-settings"]').click(); switchVersionAndVerify("v4", "v5"); - cy.get(commonWidgetSelector.draggableWidget("textInput")) - .verifyVisibleElement("have.value", "Ervin Howell"); + cy.get( + commonWidgetSelector.draggableWidget("textInput") + ).verifyVisibleElement("have.value", "Ervin Howell"); //url validation should be added after bug fix // cy.url().should("include", "/home?version=v5"); - }); }); diff --git a/cypress-tests/cypress/support/utils/apps.js b/cypress-tests/cypress/support/utils/apps.js index 0ddfd72ac7..2a6b9da2b9 100644 --- a/cypress-tests/cypress/support/utils/apps.js +++ b/cypress-tests/cypress/support/utils/apps.js @@ -135,7 +135,7 @@ export const resolveHost = () => { const baseUrl = Cypress.config("baseUrl"); const urlMapping = { - "http://localhost:8082": "http://localhost:8082", + "http://localhost:3000": "http://localhost:3000", "http://localhost:3000/apps": "http://localhost:3000/apps", "http://localhost:4001": "http://localhost:3000", "http://localhost:4001/apps": "http://localhost:3000/apps", diff --git a/cypress-tests/cypress/support/utils/commonWidget.js b/cypress-tests/cypress/support/utils/commonWidget.js index c48cd18917..b34ff0b48e 100644 --- a/cypress-tests/cypress/support/utils/commonWidget.js +++ b/cypress-tests/cypress/support/utils/commonWidget.js @@ -31,8 +31,11 @@ export const verifyAndModifyParameter = (paramName, value) => { export const openEditorSidebar = (widgetName = "") => { cy.hideTooltip(); - cy.get(`${commonWidgetSelector.draggableWidget(widgetName)}:eq(0)`).realHover() - cy.get(commonWidgetSelector.widgetConfigHandle(widgetName)).click(); + + cy.get(`${commonWidgetSelector.draggableWidget(widgetName)}:eq(0)`).realHover().then(() => { + cy.wait(1000); + cy.get(commonWidgetSelector.widgetConfigHandle(widgetName)).click(); + }) }; export const verifyAndModifyToggleFx = ( diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js index 4c1446636e..e480fce14d 100644 --- a/cypress-tests/cypress/support/utils/dataSource.js +++ b/cypress-tests/cypress/support/utils/dataSource.js @@ -25,7 +25,7 @@ export const query = (operation) => { }; export const verifypreview = (type, data) => { - cy.get(`[data-cy="preview-tab-${type}"]`).click(); + cy.get(`[data-cy="preview-tab-${type}"]`, { timeout: 15000 }).click(); cy.get(`[data-cy="preview-${type}-data-container"]`).verifyVisibleElement( "contain.text", data, @@ -255,7 +255,7 @@ export const createRestAPIQuery = ( }).then((response) => { const editingVersionId = response.body.editing_version.id; - const data_source_id = Cypress.env(kind); + const data_source_id = Cypress.env(`${dsName}`); const requestBody = { app_id: Cypress.env("appId"), diff --git a/cypress-tests/cypress/support/utils/inspector.js b/cypress-tests/cypress/support/utils/inspector.js index a5c6ba5286..67f9896fa9 100644 --- a/cypress-tests/cypress/support/utils/inspector.js +++ b/cypress-tests/cypress/support/utils/inspector.js @@ -1,60 +1,49 @@ -export const verifyNodeData = (node, type, children, index = 0) => { - cy.get( - `[data-cy="inspector-node-${node.toLowerCase()}"] > .node-length-color` - ) - .eq(index) - .realHover() - .verifyVisibleElement("have.text", `${children}`); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .verifyVisibleElement("have.text", node); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-type`) - .eq(index) - .verifyVisibleElement("have.text", type); -}; +import { commonWidgetSelector } from "Selectors/common"; -export const openNode = (node, index = 0, time = 1000) => { - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .click(); - cy.wait(time); -}; - -export const verifyValue = (node, type, children, index = 0) => { - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-2`) - .eq(index) - .realHover() - .verifyVisibleElement("contain.text", `${children}`); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .verifyVisibleElement("contain.text", node); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`) - .eq(index) - .verifyVisibleElement("contain.text", type); -}; -export const deleteComponentFromInspector = (node) => { - cy.get('[data-cy="inspector-node-components"] > .node-key').click(); - cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').last().click(); -}; - -export const verifyfunctions = (node, type, index = 0) => { - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .fs-10`) - .eq(index) - .realHover() - .verifyVisibleElement("contain.text", `${type}`); - cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`) - .eq(index) - .verifyVisibleElement("contain.text", node); - // cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`) - // .eq(index) - // .verifyVisibleElement("contain.text", type); +export const openAndVerifyNode = (nodeName, nodes, verificationFunction) => { + openStateFromComponent(nodeName); + verifyNodes(nodes, verificationFunction); }; export const verifyNodes = (nodes, verificationFunction) => { nodes.forEach(node => verificationFunction(node.key, node.type, node.value)); }; -export const openAndVerifyNode = (nodeName, nodes, verificationFunction) => { - openNode(nodeName); - verifyNodes(nodes, verificationFunction); +export const openNode = (node, index = 0, time = 1000) => { + cy.get(`[data-cy="inspector-${node.toLowerCase()}-expand-button"]`, { timeout: time }) + .eq(index) + .click(); +}; + +export const openStateFromComponent = (widgetName) => { + cy.get(commonWidgetSelector.draggableWidget(widgetName)) + .realHover() + .realHover(); + + cy.get(commonWidgetSelector.draggableWidget(widgetName)) + .realHover() + .then(() => { + cy.get(`[data-cy="${widgetName}-inspect-button"]`) + .realHover({ position: "topRight" }) + .last() + .realClick(); + }); +} + +export const verifyNodeData = (node, type, value, index = 0) => { + cy.get( + `[data-cy="inspector-${node.toLowerCase()}-label"]` + ) + .eq(index) + .realHover() + .verifyVisibleElement("have.text", `${node}`); + + cy.get(`[data-cy="inspector-${node.toLowerCase()}-value"]`) + .eq(index) + .verifyVisibleElement("have.text", type == 'Function' ? 'function' : value); +}; + +export const deleteComponentFromInspector = (node) => { + cy.get('[data-cy="inspector-menu-icon"]').click(); + cy.get(`[data-cy="inspector-delete-component-action"`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').last().click(); }; \ No newline at end of file diff --git a/cypress-tests/cypress/support/utils/restAPI.js b/cypress-tests/cypress/support/utils/restAPI.js index 2dfc225a65..f87c8d6f61 100644 --- a/cypress-tests/cypress/support/utils/restAPI.js +++ b/cypress-tests/cypress/support/utils/restAPI.js @@ -1,19 +1,31 @@ -export const createAndRunRestAPIQuery = ( +export const createAndRunRestAPIQuery = ({ queryName, dsName, method = "GET", url = "", + urlSuffix = "", headersList = [], bodyList = [], jsonBody = null, + rawBody = null, + cookiesList = [], + paramsList = [], run = true, - urlSuffix = "" -) => { + expectedResponseShape = {}, + authType = "", + authToken = "", +}) => { cy.getCookie("tj_auth_token").then((cookie) => { const headers = { "Tj-Workspace-Id": Cypress.env("workspaceId"), Cookie: `tj_auth_token=${cookie.value}`, }; + // if (authType === "bearer" || authType === "oauth2") { + // headers["Authorization"] = `Bearer ${authToken}`; + // } else if (authType === "basic") { + // headers["Authorization"] = `Basic ${btoa(authToken)}`; + // } + cy.request({ method: "GET", url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`, @@ -27,84 +39,108 @@ export const createAndRunRestAPIQuery = ( url: `${Cypress.env("server_host")}/api/data-sources/${Cypress.env("workspaceId")}/environments/${currentEnvironmentId}/versions/${editingVersionId}`, headers, }).then((dsResponse) => { - expect(dsResponse.status).to.eq(200); - const dataSource = dsResponse.body.data_sources.find( (ds) => ds.name === dsName ); - - if (!dataSource) { - throw new Error(`Data source '${dsName}' not found.`); - } - - const data_source_id = dataSource.id; - const useJsonBody = - ["POST", "PATCH", "PUT"].includes(method.toUpperCase()) && - jsonBody !== null; + const useJson = jsonBody !== null; + const useRaw = rawBody !== null; + const useForm = bodyList?.length && !useJson && !useRaw; const queryOptions = { method: method.toLowerCase(), url: url + urlSuffix, - url_params: [["", ""]], + url_params: paramsList.length ? paramsList : [["", ""]], headers: headersList.length ? headersList : [["", ""]], - body: !useJsonBody && bodyList.length ? bodyList : [["", ""]], - json_body: useJsonBody ? jsonBody : null, - body_toggle: useJsonBody, + cookies: cookiesList.length ? cookiesList : [["", ""]], + body: useForm ? bodyList : [["", ""]], + json_body: useJson ? jsonBody : null, + raw_body: useRaw ? rawBody : "", + body_toggle: useJson || useRaw, 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, + data_source_id: dataSource.id, plugin_id: null, }; cy.request({ method: "POST", - url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`, + url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${dataSource.id}/versions/${editingVersionId}`, headers, body: requestBody, }).then((createResponse) => { - expect(createResponse.status).to.equal(201); + expect(createResponse.status).to.eq(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, + failOnStatusCode: false, }).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 responseData = runResponse.body?.data; + const requestHeaders = + runResponse.body?.metadata?.request?.headers || {}; + + if (runResponse.body.status === "ok") { + expect([200, 201]).to.include(runResponse.status); + cy.log("Response:", responseData); + if ( + expectedResponseShape && + typeof expectedResponseShape === "object" + ) { + Object.entries(expectedResponseShape).forEach( + ([path, expected]) => { + const value = path + .split(".") + .reduce((obj, key) => obj?.[key], responseData); + + if (expected === true) { + expect(value).to.not.be.undefined; + } else { + expect(value).to.eq(expected); + } + } + ); + } + + const expectedContentType = headersList.find( + ([key]) => key.toLowerCase() === "content-type" + )?.[1]; + if (expectedContentType && requestHeaders["content-type"]) { + expect(requestHeaders["content-type"]).to.include( + expectedContentType + ); + } + if (Array.isArray(responseData)) { + responseData.forEach((item) => { + expect(item).to.have.any.keys("id", "name", "price"); + }); + } + if (responseData?.id) { + cy.writeFile("cypress/fixtures/restAPI/storedId.json", { + id: responseData.id, + }); + } + } else if (runResponse.body.status === "failed") { + expect(runResponse.body.message).to.eq( + "Query could not be completed" + ); + const statusCode = + runResponse.body?.metadata?.response?.statusCode; + expect([400, 401, 403, 404, 500]).to.include(statusCode); + cy.log( + "Failure validated as expected with status:", + statusCode + ); } }); } diff --git a/docker/cloud/cloud-entrypoint.sh b/docker/cloud/cloud-entrypoint.sh new file mode 100644 index 0000000000..26df38cb5c --- /dev/null +++ b/docker/cloud/cloud-entrypoint.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +npm cache clean --force + +if [ -d "./server/dist" ]; then + SETUP_CMD='npm run cloud:setup:prod' +else + SETUP_CMD='npm run cloud:setup' +fi + +npm cache clean --force + +if [ -f "./.env" ]; then + declare $(grep -v '^#' ./.env | xargs) +fi + +if [ -z "$DATABASE_URL" ]; then + ./server/scripts/wait-for-it.sh $PG_HOST:${PG_PORT:-5432} --strict --timeout=300 -- $SETUP_CMD +else + PG_HOST=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $6}') + PG_PORT=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $7}') + + if [ -z "$DATABASE_PORT" ]; then + DATABASE_PORT="5432" + fi + + ./server/scripts/wait-for-it.sh "$PG_HOST:$PG_PORT" --strict --timeout=300 -- $SETUP_CMD +fi + +exec "$@" diff --git a/docker/cloud/cloud-production.Dockerfile b/docker/cloud/cloud-production.Dockerfile new file mode 100644 index 0000000000..a2e36bedb3 --- /dev/null +++ b/docker/cloud/cloud-production.Dockerfile @@ -0,0 +1,121 @@ +FROM node:18.18.2-buster AS builder + +# Fix for JS heap limit allocation issue +ENV NODE_OPTIONS="--max-old-space-size=4096" + +RUN npm i -g npm@9.8.1 +RUN mkdir -p /app +# RUN npm cache clean --force + +WORKDIR /app + +# Scripts for building +COPY ./package.json ./package.json + +# Build plugins +COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ +RUN npm --prefix plugins install +COPY ./plugins/ ./plugins/ +RUN NODE_ENV=production npm --prefix plugins run build +RUN npm --prefix plugins prune --production + +# Build frontend +COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/ +RUN npm --prefix frontend install +COPY ./frontend/ ./frontend/ +RUN npm --prefix frontend run build --production +RUN npm --prefix frontend prune --production + +ENV NODE_ENV=production + +# Build server +COPY ./server/package.json ./server/package-lock.json ./server/ +RUN npm --prefix server install +COPY ./server/ ./server/ +RUN npm install -g @nestjs/cli +RUN npm --prefix server run build + +FROM debian:11 + +RUN apt-get update -yq \ + && apt-get install curl gnupg zip -yq \ + && apt-get install -yq build-essential \ + && apt-get clean -y + + +RUN curl -O https://nodejs.org/dist/v18.18.2/node-v18.18.2-linux-x64.tar.xz \ + && tar -xf node-v18.18.2-linux-x64.tar.xz \ + && mv node-v18.18.2-linux-x64 /usr/local/lib/nodejs \ + && echo 'export PATH="/usr/local/lib/nodejs/bin:$PATH"' >> /etc/profile.d/nodejs.sh \ + && /bin/bash -c "source /etc/profile.d/nodejs.sh" \ + && rm node-v18.18.2-linux-x64.tar.xz +ENV PATH=/usr/local/lib/nodejs/bin:$PATH + +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 && \ + apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes && \ + apt-get -y autoremove && \ + apt-get -y autoclean + +# Install Instantclient Basic Light Oracle and Dependencies +WORKDIR /opt/oracle + +RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \ + wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \ + unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \ + cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \ + echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig +# Set the Instant Client library paths +ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}" + + +WORKDIR / + +RUN mkdir -p /app + +# copy npm scripts +COPY --from=builder /app/package.json ./app/package.json + +# copy plugins dependencies +COPY --from=builder /app/plugins/dist ./app/plugins/dist +COPY --from=builder /app/plugins/client.js ./app/plugins/client.js +COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules +COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common +COPY --from=builder /app/plugins/package.json ./app/plugins/package.json + +# copy server build +COPY --from=builder /app/server/package.json ./app/server/package.json +COPY --from=builder /app/server/.version ./app/server/.version +COPY --from=builder /app/server/node_modules ./app/server/node_modules +COPY --from=builder /app/server/templates ./app/server/templates +COPY --from=builder /app/server/scripts ./app/server/scripts +COPY --from=builder /app/server/dist ./app/server/dist + +COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh + +# Define non-sudo user +RUN useradd --create-home --home-dir /home/appuser appuser \ + && chown -R appuser:0 /app \ + && chown -R appuser:0 /home/appuser \ + && chmod u+x /app \ + && chmod -R g=u /app + +# Set npm cache directory +ENV npm_config_cache /home/appuser/.npm + +ENV HOME=/home/appuser + +# Installing git for simple git commands +RUN apt-get update && apt-get install -y git && apt-get clean + +USER appuser + +WORKDIR /app +# Dependencies for scripts outside nestjs +RUN npm install dotenv@10.0.0 joi@17.4.1 + +ENTRYPOINT ["./server/cloud-entrypoint.sh"] diff --git a/docker/cloud/cloud-server.Dockerfile b/docker/cloud/cloud-server.Dockerfile index cc9fd4fce3..96a9fe0f01 100644 --- a/docker/cloud/cloud-server.Dockerfile +++ b/docker/cloud/cloud-server.Dockerfile @@ -91,12 +91,13 @@ COPY --from=builder /app/plugins/package.json ./app/plugins/package.json # copy server build COPY --from=builder /app/server/package.json ./app/server/package.json COPY --from=builder /app/server/.version ./app/server/.version -COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh COPY --from=builder /app/server/node_modules ./app/server/node_modules COPY --from=builder /app/server/templates ./app/server/templates COPY --from=builder /app/server/scripts ./app/server/scripts COPY --from=builder /app/server/dist ./app/server/dist +COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh + # Define non-sudo user RUN useradd --create-home --home-dir /home/appuser appuser \ && chown -R appuser:0 /app \ @@ -108,10 +109,14 @@ RUN useradd --create-home --home-dir /home/appuser appuser \ ENV npm_config_cache /home/appuser/.npm ENV HOME=/home/appuser + +# Installing git for simple git commands +RUN apt-get update && apt-get install -y git && apt-get clean + USER appuser WORKDIR /app # Dependencies for scripts outside nestjs RUN npm install dotenv@10.0.0 joi@17.4.1 -ENTRYPOINT ["./server/entrypoint.sh"] +ENTRYPOINT ["./server/cloud-entrypoint.sh"] diff --git a/docker/ee/ee-entrypoint.sh b/docker/ee/ee-entrypoint.sh index ac4b0bafd2..f9319e16be 100755 --- a/docker/ee/ee-entrypoint.sh +++ b/docker/ee/ee-entrypoint.sh @@ -42,6 +42,115 @@ else echo "Using external PostgREST at $PGRST_HOST." fi +# Neo4j configuration +# ---------------------------------- +# Default Neo4j environment values +# ---------------------------------- +export NEO4J_USER=${NEO4J_USER:-"neo4j"} +export NEO4J_PASSWORD=${NEO4J_PASSWORD:-"appaqvyvRLbeukhFE"} +export NEO4J_AUTH=${NEO4J_AUTH:-"neo4j/appaqvyvRLbeukhFE"} +export NEO4J_URI=${NEO4J_URI:-"bolt://localhost:7687"} +export NEO4J_PLUGINS=${NEO4J_PLUGINS:-'["apoc"]'} +export NEO4J_AUTH + +# Extract username and password from NEO4J_AUTH if set +if [ -n "$NEO4J_AUTH" ]; then + # Extract username and password from NEO4J_AUTH (format: username/password) + NEO4J_USERNAME=$(echo "$NEO4J_AUTH" | cut -d'/' -f1) + NEO4J_PASSWORD=$(echo "$NEO4J_AUTH" | cut -d'/' -f2) + + # Export these for application use + export NEO4J_USERNAME + export NEO4J_PASSWORD + + echo "Neo4j authentication configured with username: $NEO4J_USERNAME" +else + echo "NEO4J_AUTH not set, using default authentication" +fi + +# Check if Neo4j is already initialized and set password if necessary +if [ "$NEO4J_AUTH" != "none" ] && [ -n "$NEO4J_PASSWORD" ]; then + echo "Setting Neo4j initial password..." + + # Ensure Neo4j is not running before setting the initial password + neo4j stop || true + + # Set the initial password using the correct command format for Neo4j 5.x + NEO4J_ADMIN_CMD=$(which neo4j-admin) + NEO4J_VERSION=$(neo4j --version | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+" | head -n 1) + echo "Detected Neo4j version: $NEO4J_VERSION" + + # Use version-specific command format + MAJOR_VERSION=$(echo $NEO4J_VERSION | cut -d. -f1) + if [ "$MAJOR_VERSION" -ge "5" ]; then + # For Neo4j 5.x and higher + echo "Using Neo4j 5.x+ password command format" + $NEO4J_ADMIN_CMD dbms set-initial-password "$NEO4J_PASSWORD" --require-password-change=false >/dev/null 2>&1 || { + echo "Warning: Could not set Neo4j password, it may already be set" + } + else + # For Neo4j 4.x and lower + echo "Using Neo4j 4.x password command format" >/dev/null 2>&1 + $NEO4J_ADMIN_CMD set-initial-password "$NEO4J_PASSWORD" >/dev/null 2>&1 || { + echo "Warning: Could not set Neo4j password, it may already be set" + } + fi +fi + +# Update Neo4j configuration +echo "Configuring Neo4j..." +cat > /etc/neo4j/neo4j.conf << EOF +# Neo4j configuration +dbms.security.auth_enabled=true +server.bolt.enabled=true +server.bolt.listen_address=0.0.0.0:7687 +server.directories.data=/var/lib/neo4j/data +server.directories.logs=/var/log/neo4j +initial.dbms.default_database=neo4j +server.directories.plugins=/var/lib/neo4j/plugins +server.directories.import=/var/lib/neo4j/import + +# APOC Settings +dbms.security.procedures.unrestricted=apoc.* +dbms.security.procedures.allowlist=apoc.*,algo.*,gds.* +EOF + +if [ -w "$NEO4J_LOG_DIR" ]; then + chmod -R 770 "$NEO4J_LOG_DIR" || echo "Warning: Could not set log directory permissions" >/dev/null 2>&1 +fi + +# Start Neo4j +echo "Starting Neo4j service..." +neo4j console >/dev/null 2>&1 & + +# Add a wait for Neo4j to be ready with more robust checking +echo "Waiting for Neo4j to be ready..." +NEO4J_READY=false +for i in {1..60}; do + # First try standard status check + if neo4j status >/dev/null 2>&1; then + echo "Neo4j is ready (via status check)" + NEO4J_READY=true + break + fi + + # Also try connecting to the bolt port as a fallback + if command -v nc >/dev/null 2>&1; then + if nc -z localhost 7687 >/dev/null 2>&1; then + echo "Neo4j is ready (port 7687 is open)" + NEO4J_READY=true + break + fi + fi + + echo "Waiting for Neo4j to start... ($i/60)" + sleep 2 +done + +if [ "$NEO4J_READY" = false ]; then + echo "WARNING: Neo4j may not be fully started yet, but continuing..." +fi + # Check WORKLOW_WORKER and skip SETUP_CMD if true if [ "${WORKFLOW_WORKER}" == "true" ]; then echo "WORKFLOW_WORKER is set to true. Running worker process." diff --git a/docker/ee/ee-production.Dockerfile b/docker/ee/ee-production.Dockerfile index e611643f30..e7405f0994 100644 --- a/docker/ee/ee-production.Dockerfile +++ b/docker/ee/ee-production.Dockerfile @@ -60,7 +60,7 @@ RUN npm --prefix server run build FROM debian:11 RUN apt-get update -yq \ - && apt-get install curl gnupg zip -yq \ + && apt-get install curl wget gnupg zip -yq \ && apt-get install -yq build-essential \ && apt -y install redis \ && apt-get clean -y @@ -80,13 +80,29 @@ RUN echo "[supervisord]\n" \ "nodaemon=true\n" \ "\n" \ "[program:postgrest]\n" \ - "command=/bin/postgrest \n" \ + "command=/bin/postgrest\n" \ "autostart=true\n" \ "autorestart=true\n" \ "stdout_logfile=/dev/stdout\n" \ "stderr_logfile=/dev/stderr\n" \ "stdout_logfile_maxbytes=0\n" \ "stderr_logfile_maxbytes=0\n" \ + "\n" \ + "[program:neo4j]\n" \ + "command=neo4j console\n" \ + "autostart=true\n" \ + "autorestart=unexpected\n" \ + "startsecs=30\n" \ + "startretries=999\n" \ + "priority=90\n" \ + "exitcodes=0,1,2\n" \ + "stopsignal=SIGTERM\n" \ + "stopasgroup=true\n" \ + "killasgroup=true\n" \ + "redirect_stderr=true\n" \ + "stdout_logfile=/var/log/neo4j/neo4j.log\n" \ + "stdout_logfile_backups=10\n" \ + "stderr_capture_maxbytes=20MB\n" \ "\n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf # Create a wrapper for PostgREST to prefix its logs @@ -114,6 +130,48 @@ RUN apt-get update && \ apt-get -y autoremove && \ apt-get -y autoclean +# Install Neo4j +RUN wget -O - https://debian.neo4j.com/neotechnology.gpg.key | apt-key add - && \ + echo "deb https://debian.neo4j.com stable 5" > /etc/apt/sources.list.d/neo4j.list && \ + apt-get update && \ + apt-get install -y neo4j=1:5.26.6 && \ + apt-mark hold neo4j && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set the necessary Neo4j environment variables +ENV NEO4J_HOME=/opt/neo4j +ENV NEO4J_CONF=/etc/neo4j +ENV NEO4J_DATA=/var/lib/neo4j/data +ENV NEO4J_LOG=/var/log/neo4j +ENV NEO4J_PLUGIN=/var/lib/neo4j/plugins +ENV NEO4J_IMPORT=/var/lib/neo4j/import + +# Create the necessary directories for Neo4j +RUN mkdir -p /data/db /data/logs /data/plugins +RUN mkdir -p /opt/neo4j/plugins + +# Configure APOC plugin for Neo4j +ENV NEO4J_dbms_active_plugins=apoc + +# Download and install APOC plugin for Neo4j 5.x (BEFORE creating user) +RUN mkdir -p /var/lib/neo4j/plugins && \ + wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-core.jar && \ + # Try to download extended version + (wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-extended.jar || \ + wget -P /var/lib/neo4j/plugins https://neo4j-contrib.github.io/neo4j-apoc-procedures/5.26.6/apoc-5.26.6-extended.jar || \ + echo "Extended JAR not available, continuing with core only") + +# Configure Neo4j with APOC +RUN echo "dbms.security.procedures.unrestricted=apoc.*" >> /etc/neo4j/neo4j.conf && \ + echo "dbms.security.procedures.allowlist=apoc.*,algo.*,gds.*" >> /etc/neo4j/neo4j.conf && \ + echo "dbms.directories.plugins=/var/lib/neo4j/plugins" >> /etc/neo4j/neo4j.conf + +# Configure Neo4j to use authentication +RUN if [ -f "/etc/neo4j/neo4j.conf" ]; then \ + sed -i '/dbms.security.auth_enabled/d' /etc/neo4j/neo4j.conf && \ + echo "dbms.security.auth_enabled=true" >> /etc/neo4j/neo4j.conf; \ +fi + # Install Instantclient Basic Light Oracle and Dependencies WORKDIR /opt/oracle @@ -149,6 +207,7 @@ COPY --from=builder /app/server/node_modules ./app/server/node_modules COPY --from=builder /app/server/templates ./app/server/templates COPY --from=builder /app/server/scripts ./app/server/scripts COPY --from=builder /app/server/dist ./app/server/dist +COPY --from=builder /app/server/src/assets ./app/server/src/assets COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh @@ -161,14 +220,21 @@ RUN useradd --create-home --home-dir /home/appuser appuser \ && chmod -R g=u /app \ && chmod -R g=u /home -# Create directory /home/appuser and set ownership to appuser (Refer doc for understanding the changes https://app.clickup.com/37484951/v/dc/13qycq-4081) +RUN mkdir -p /var/lib/neo4j/data/databases /var/lib/neo4j/data/transactions /var/log/neo4j /opt/neo4j/run && \ + chown -R appuser:0 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \ + chmod -R 770 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \ + chmod -R 644 /var/lib/neo4j/plugins/*.jar && \ + chown -R appuser:0 /var/lib/neo4j/plugins && \ + chmod 755 /var/lib/neo4j/plugins + +# Create directory /home/appuser and set ownership to appuser RUN mkdir -p /home/appuser \ && chown -R appuser:0 /home/appuser \ && chmod g+s /home/appuser \ && chmod -R g=u /home/appuser \ && npm cache clean --force -# Create directory /tmp/.npm/npm-cache/ and set ownership to appuser (Refer doc for understanding the changes https://app.clickup.com/37484951/v/dc/13qycq-4081) +# Create directory /tmp/.npm/npm-cache/ and set ownership to appuser RUN mkdir -p /tmp/.npm/npm-cache/ \ && chown -R appuser:0 /tmp/.npm/npm-cache/ \ && chmod g+s /tmp/.npm/npm-cache/ \ @@ -206,6 +272,9 @@ RUN mkdir -p /var/lib/postgrest /var/log/postgrest /etc/postgrest \ ENV HOME=/home/appuser +# Installing git for simple git commands +RUN apt-get update && apt-get install -y git && apt-get clean + # Switch back to appuser USER appuser diff --git a/frontend/.version b/frontend/.version index 4eba2a62eb..f982feb41b 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.13.0 +3.14.0 diff --git a/frontend/assets/images/icons/editor/file-code.svg b/frontend/assets/images/icons/editor/file-code.svg new file mode 100644 index 0000000000..4dc470055c --- /dev/null +++ b/frontend/assets/images/icons/editor/file-code.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/images/icons/module-editor.svg b/frontend/assets/images/icons/module-editor.svg new file mode 100644 index 0000000000..e0b55223c3 --- /dev/null +++ b/frontend/assets/images/icons/module-editor.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module-list-icon-1.svg b/frontend/assets/images/modules/blank-module-list-icon-1.svg new file mode 100644 index 0000000000..1f9c188e27 --- /dev/null +++ b/frontend/assets/images/modules/blank-module-list-icon-1.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module-list-icon-2.svg b/frontend/assets/images/modules/blank-module-list-icon-2.svg new file mode 100644 index 0000000000..4ca21df25a --- /dev/null +++ b/frontend/assets/images/modules/blank-module-list-icon-2.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module.svg b/frontend/assets/images/modules/blank-module.svg new file mode 100644 index 0000000000..35207b8ca1 --- /dev/null +++ b/frontend/assets/images/modules/blank-module.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2db11e6493..4f841128fc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -89,6 +89,7 @@ "query-string": "^8.1.0", "rc-slider": "^10.1.1", "react": "^18.2.0", + "react-accessible-treeview": "^2.11.1", "react-beautiful-dnd": "^13.1.1", "react-big-calendar": "^1.6.5", "react-bootstrap": "^2.7.2", @@ -105,6 +106,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-highlight-words": "^0.21.0", "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.3.5", "react-i18next": "^12.1.5", @@ -34032,6 +34034,11 @@ "hermes-estree": "0.23.1" } }, + "node_modules/highlight-words-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.3.tgz", + "integrity": "sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "license": "BSD-3-Clause", @@ -41074,6 +41081,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-accessible-treeview": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/react-accessible-treeview/-/react-accessible-treeview-2.11.1.tgz", + "integrity": "sha512-lFegHjFJp2OvtoHMtbIqjby7N3MGDRASlbJsMLqElxQHwZ97xIYho2S4QvXKK7l3/nII0IKDQFJXZNBj6ecG3g==", + "peerDependencies": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-base16-styling": { "version": "0.9.1", "license": "MIT", @@ -41616,6 +41634,23 @@ } } }, + "node_modules/react-highlight-words": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/react-highlight-words/-/react-highlight-words-0.21.0.tgz", + "integrity": "sha512-SdWEeU9fIINArEPO1rO5OxPyuhdEKZQhHzZZP1ie6UeXQf+CjycT1kWaB+9bwGcVbR0NowuHK3RqgqNg6bgBDQ==", + "dependencies": { + "highlight-words-core": "^1.2.0", + "memoize-one": "^4.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/react-highlight-words/node_modules/memoize-one": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", + "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" + }, "node_modules/react-hot-toast": { "version": "2.4.1", "license": "MIT", diff --git a/frontend/package.json b/frontend/package.json index 3821a370f5..22f34ae313 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -85,6 +85,7 @@ "query-string": "^8.1.0", "rc-slider": "^10.1.1", "react": "^18.2.0", + "react-accessible-treeview": "^2.11.1", "react-beautiful-dnd": "^13.1.1", "react-big-calendar": "^1.6.5", "react-bootstrap": "^2.7.2", @@ -101,6 +102,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-highlight-words": "^0.21.0", "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.3.5", "react-i18next": "^12.1.5", @@ -264,4 +266,4 @@ "jsx" ] } -} \ No newline at end of file +} diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index 3d58259c5f..e2967e892d 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -2,7 +2,7 @@ import React, { Suspense } from 'react'; // eslint-disable-next-line no-unused-vars import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import { authorizeWorkspace, updateCurrentSession } from '@/_helpers/authorizeWorkspace'; -import { authenticationService, tooljetService } from '@/_services'; +import { authenticationService, tooljetService, licenseService } from '@/_services'; import { withRouter } from '@/_hoc/withRouter'; import { PrivateRoute, AdminRoute, AppsRoute, SwitchWorkspaceRoute } from '@/Routes'; import { HomePage } from '@/HomePage'; @@ -42,7 +42,6 @@ import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; import { checkIfToolJetCloud } from '@/_helpers/utils'; import { BasicPlanMigrationBanner } from '@/HomePage/BasicPlanMigrationBanner/BasicPlanMigrationBanner'; -import { licenseService } from '@/_services'; const AppWrapper = (props) => { const { isAppDarkMode } = useAppDarkMode(); @@ -295,6 +294,15 @@ class AppComponent extends React.Component { > }> }> + + + + } + /> {getAuditLogsRoutes(this.props)} import('@/AppBuilder/Header')); // const LeftSidebar = lazy(() => import('@/AppBuilder/LeftSidebar')); @@ -22,12 +23,13 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; // const QueryPanel = lazy(() => import('@/AppBuilder/QueryPanel')); // TODO: split Loader into separate component and remove editor loading state from Editor -export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode }) => { +export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType = 'front-end' }) => { useAppData(appId, moduleId, darkMode); - const isEditorLoading = useStore((state) => state.isEditorLoading); - const currentMode = useStore((state) => state.currentMode); + const isEditorLoading = useStore((state) => state.loaderStore.modules[moduleId].isEditorLoading, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); + const isModuleEditor = appType === 'module'; - const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode); + const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode, shallow); const changeToDarkMode = (newMode) => { updateIsTJDarkMode(newMode); @@ -45,19 +47,19 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod return (
- Loading...
}> - - - - {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - - - + + Loading...}> + + + + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } + + - - - + + + ); diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 365c3bbc59..10ab1fa054 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Container } from './Container'; import Grid from './Grid'; import { EditorSelecto } from './Selecto'; -import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { HotkeyProvider } from './HotkeyProvider'; import './appCanvas.scss'; import useStore from '@/AppBuilder/_stores/store'; @@ -18,15 +18,18 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { +export const AppCanvas = ({ appId, isViewerSidebarPinned, isViewer = false }) => { + const { moduleId, isModuleMode, appType } = useModuleContext(); const canvasContainerRef = useRef(); const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow); - const canvasHeight = useStore((state) => state.canvasHeight); - const creationMode = useStore((state) => state.app.creationMode); - const environmentLoadingState = useStore((state) => state.environmentLoadingState || state.isEditorLoading); - const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth()); + const canvasHeight = useStore((state) => state.appStore.modules[moduleId].canvasHeight); + const creationMode = useStore((state) => state.appStore.modules[moduleId].app.creationMode); + const environmentLoadingState = useStore( + (state) => state.environmentLoadingState || state.loaderStore.modules[moduleId].isEditorLoading + ); + const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth(moduleId)); const gridWidth = canvasWidth / NO_OF_GRIDS; - const currentMode = useStore((state) => state.currentMode, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const pageSidebarStyle = useStore((state) => state?.pageSettings?.definition?.properties?.style, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const queryPanelHeight = useStore((state) => state?.queryPanel?.queryPanelHeight || 0); @@ -42,23 +45,79 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const getPageId = useStore((state) => state.getCurrentPageId, shallow); + const hideSidebar = isModuleMode || isPagesSidebarHidden || appType === 'module'; + useEffect(() => { // Need to remove this if we shift setExposedVariable Logic outside of components // Currently present to run onLoadQueries after the component is mounted - setIsComponentLayoutReady(true); - return () => setIsComponentLayoutReady(false); + setIsComponentLayoutReady(true, moduleId); + return () => setIsComponentLayoutReady(false, moduleId); }, []); useEffect(() => { function handleResize() { - const _canvasWidth = document.getElementById('real-canvas')?.getBoundingClientRect()?.width; + const _canvasWidth = + moduleId === 'canvas' + ? document.getElementById('real-canvas')?.getBoundingClientRect()?.width + : document.getElementById(moduleId)?.getBoundingClientRect()?.width; if (_canvasWidth !== 0) setCanvasWidth(_canvasWidth); } - window.addEventListener('resize', handleResize); + + if (moduleId === 'canvas') { + window.addEventListener('resize', handleResize); + } else { + const elem = document.getElementById(moduleId); + const resizeObserver = new ResizeObserver(handleResize); + if (elem) resizeObserver.observe(elem); + + return () => { + if (elem) resizeObserver.unobserve(elem); + resizeObserver.disconnect(); + }; + } handleResize(); return () => window.removeEventListener('resize', handleResize); - }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned]); + }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned, moduleId]); + + const styles = useMemo(() => { + const canvasBgColor = + currentMode === 'view' + ? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor) + : !isAppDarkMode + ? '#EBEBEF' + : '#2F3C4C'; + + if (isModuleMode) { + return { + borderLeft: 'none', + height: '100%', + background: canvasBgColor, + }; + } + + return { + borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid', + height: currentMode === 'edit' ? canvasContainerHeight : '100%', + background: canvasBgColor, + marginLeft: + isViewerSidebarPinned && !hideSidebar && currentLayout !== 'mobile' && currentMode !== 'edit' + ? pageSidebarStyle === 'icon' + ? '65px' + : '210px' + : 'auto', + }; + }, [ + currentMode, + isAppDarkMode, + isModuleMode, + editorMarginLeft, + canvasContainerHeight, + isViewerSidebarPinned, + hideSidebar, + currentLayout, + pageSidebarStyle, + ]); return (
{ className={cx( 'canvas-container align-items-center page-container', { 'dark-theme theme-dark': isAppDarkMode, close: !isViewerSidebarPinned }, - { 'overflow-x-auto': (currentMode === 'edit' && isSidebarOpen) || currentMode === 'view' } + { 'overflow-x-auto': (currentMode === 'edit' && isSidebarOpen) || currentMode === 'view' }, + { 'overflow-x-hidden': moduleId !== 'canvas' } // Disbling horizontal scroll for modules in view mode )} - style={{ - // transform: `scale(1)`, - borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid', - height: currentMode === 'edit' ? canvasContainerHeight : '100%', - background: - currentMode === 'view' - ? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor) - : !isAppDarkMode - ? '#EBEBEF' - : '#2F3C4C', - marginLeft: - isViewerSidebarPinned && !isPagesSidebarHidden && currentLayout !== 'mobile' && currentMode !== 'edit' - ? pageSidebarStyle === 'icon' - ? '65px' - : '210px' - : 'auto', - }} + style={styles} >
@@ -107,7 +151,7 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { {environmentLoadingState !== 'loading' && (
{ canvasMaxWidth={canvasMaxWidth} isViewerSidebarPinned={isViewerSidebarPinned} pageSidebarStyle={pageSidebarStyle} + appType={appType} /> -
+ {appType !== 'module' &&
}
)} diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 45835c39de..22a410c674 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -4,6 +4,8 @@ import './configHandle.scss'; import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; import SolidIcon from '@/_ui/Icon/solidIcons/index'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; +import { DROPPABLE_PARENTS } from '../appCanvasConstants'; const CONFIG_HANDLE_HEIGHT = 20; const BUFFER_HEIGHT = 1; @@ -18,10 +20,12 @@ export const ConfigHandle = ({ showHandle, componentType, visibility, + isModuleContainer, subContainerIndex, }) => { + const { moduleId } = useModuleContext(); const shouldFreeze = useStore((state) => state.getShouldFreeze()); - const componentName = useStore((state) => state.getComponentDefinition(id)?.component?.name || '', shallow); + const componentName = useStore((state) => state.getComponentDefinition(id, moduleId)?.component?.name || '', shallow); const isMultipleComponentsSelected = useStore( (state) => (findHighestLevelofSelection(state?.selectedComponents)?.length > 1 ? true : false), shallow @@ -43,6 +47,7 @@ export const ConfigHandle = ({ return ( (subContainerIndex === 0 || subContainerIndex === null) && (isWidgetHovered || + isModuleContainer || (showHandle && (!isMultipleComponentsSelected || (isModal && isModalOpen)) && !anyComponentHovered)) ); }, shallow); @@ -67,7 +72,9 @@ export const ConfigHandle = ({ if (componentType === 'Tabs') { setFocusedParentId(`${id}-${currentTab}`); } else { - setFocusedParentId(id); + if (DROPPABLE_PARENTS.has(componentType)) { + setFocusedParentId(id); + } } }} > @@ -125,20 +132,22 @@ export const ConfigHandle = ({ data-cy={`${componentName.toLowerCase()}-inspect-button`} className="config-handle-inspect" /> - { - deleteComponents([id]); - }} - data-cy={`${componentName.toLowerCase()}-delete-button`} - > - - + {!isModuleContainer && ( + { + deleteComponents([id]); + }} + data-cy={`${componentName.toLowerCase()}-delete-button`} + > + + + )}
)} diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index c320c3f0cb..b90eb55380 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -25,7 +25,10 @@ import NoComponentCanvasContainer from './NoComponentCanvasContainer'; import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; +import { ModuleContainerBlank } from '@/modules/Modules/components'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import useSortedComponents from '../_hooks/useSortedComponents'; +import { noop } from 'lodash'; //TODO: Revisit the logic of height (dropRef) @@ -50,9 +53,12 @@ export const Container = React.memo( isViewerSidebarPinned, pageSidebarStyle, componentType, + appType, }) => { + const { moduleId } = useModuleContext(); const realCanvasRef = useRef(null); - const components = useStore((state) => state.getContainerChildrenMapping(id), shallow); + const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow); + const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow); @@ -61,12 +67,14 @@ export const Container = React.memo( shallow ); const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); - const currentMode = useStore((state) => state.currentMode, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); + const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; + const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; - }, [componentType, index, currentMode]); + }, [index, componentType, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', @@ -75,7 +83,9 @@ export const Container = React.memo( item.canvasId = id; item.canvasWidth = getContainerCanvasWidth(); }, - drop: async ({ componentType }, monitor) => { + drop: async ({ componentType, component }, monitor) => { + setShowModuleBorder(false); // Hide the module border when dropping + if (currentMode === 'view' || (appType === 'module' && componentType !== 'ModuleContainer')) return; const didDrop = monitor.didDrop(); if (didDrop) return; if (componentType === 'PDF' && !isPDFSupported()) { @@ -84,15 +94,41 @@ export const Container = React.memo( ); return; } + + // IMPORTANT: This logic needs to be changed when we implement the module versioning + const moduleInfo = component?.moduleId + ? { + moduleId: component.moduleId, + versionId: component.versionId, + environmentId: component.environmentId, + moduleName: component.displayName, + moduleContainer: component.moduleContainer, + } + : undefined; + if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentType)) { - const parentComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id); + const parentComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout); const newComponents = [parentComponent, ...childComponents]; await addComponentToCurrentPage(newComponents); // setSelectedComponents([parentComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); } else { - const newComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id); + const newComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); await addComponentToCurrentPage([newComponent]); // setSelectedComponents([newComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); @@ -103,7 +139,11 @@ export const Container = React.memo( }), }); - const showEmptyContainer = currentMode === 'edit' && id === 'canvas' && components.length === 0 && !isOverCurrent; + const showEmptyContainer = + currentMode === 'edit' && + (id === 'canvas' || componentType === 'ModuleContainer') && + components.length === 0 && + !isOverCurrent; function getContainerCanvasWidth() { if (canvasWidth !== undefined) { @@ -115,7 +155,7 @@ export const Container = React.memo( } const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS; useEffect(() => { - useGridStore.getState().actions.setSubContainerWidths(id, getContainerCanvasWidth() / NO_OF_GRIDS); + useGridStore.getState().actions.setSubContainerWidths(id, gridWidth); // eslint-disable-next-line react-hooks/exhaustive-deps }, [canvasWidth, listViewMode, columns]); @@ -125,7 +165,8 @@ export const Container = React.memo( !isPagesSidebarHidden && isViewerSidebarPinned && currentLayout !== 'mobile' && - currentMode !== 'edit' + currentMode !== 'edit' && + appType !== 'module' ) { return `calc(100% - ${pageSidebarStyle === 'icon' ? '65px' : '210px'})`; } @@ -147,6 +188,23 @@ export const Container = React.memo( [setLastCanvasClickPosition] ); + /* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag. + Hence display is set to none on dragover and removed only when the component is added */ + + const renderEmptyContainer = () => { + if (components && components?.length !== 0) return; + + const styles = { + display: showEmptyContainer ? 'block' : 'none', + ...(componentType === 'ModuleContainer' ? { height: '100%', width: '100%' } : {}), + }; + + return ( +
+ {componentType === 'ModuleContainer' ? : } +
+ ); + }; const sortedComponents = useSortedComponents(components, currentLayout, id); return ( @@ -182,7 +240,7 @@ export const Container = React.memo( }} className={cx('real-canvas', { 'sub-canvas': id !== 'canvas', - 'show-grid': isOverCurrent && (index === 0 || index === null), + 'show-grid': isOverCurrent && (index === 0 || index === null) && currentMode === 'edit', })} id={id === 'canvas' ? 'real-canvas' : `canvas-${id}`} data-cy="real-canvas" @@ -216,14 +274,7 @@ export const Container = React.memo( /> ))}
- - {/* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag. - Hence display is set to none on dragover and removed only when the component is added */} - {(!components || components?.length === 0) && ( -
- -
- )} + {renderEmptyContainer()}
); } diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 5ece3710de..d92efe63fb 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -28,8 +28,8 @@ import { import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; -import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; - +import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'], @@ -38,17 +38,19 @@ const RESIZABLE_CONFIG = { export const GRID_HEIGHT = 10; export default function Grid({ gridWidth, currentLayout }) { + const { moduleId, isModuleEditor } = useModuleContext(); const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); const setComponentLayout = useStore((state) => state.setComponentLayout, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const [boxList, setBoxList] = useState([]); - const currentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow); + const currentPageComponents = useStore((state) => state.getCurrentPageComponents(moduleId), shallow); const selectedComponents = useStore((state) => state.selectedComponents, shallow); const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow); const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const isGroupHandleHoverd = useIsGroupHandleHoverd(); + const openModalWidgetId = useOpenModalWidgetId(); const moveableRef = useRef(null); const triggerCanvasUpdater = useStore((state) => state.triggerCanvasUpdater, shallow); @@ -132,7 +134,7 @@ export default function Grid({ gridWidth, currentLayout }) { const noOfBoxs = Object.values(boxList || []).length; useEffect(() => { - updateCanvasBottomHeight(boxList); + updateCanvasBottomHeight(boxList, moduleId); noOfBoxs != 0; // eslint-disable-next-line react-hooks/exhaustive-deps }, [noOfBoxs, triggerCanvasUpdater]); @@ -333,8 +335,8 @@ export default function Grid({ gridWidth, currentLayout }) { }; const isComponentVisible = (id) => { - const component = getResolvedComponent(id); - const componentExposedVisibility = getExposedValueOfComponent(id)?.isVisible; + const component = getResolvedComponent(id, null, moduleId); + const componentExposedVisibility = getExposedValueOfComponent(id, moduleId)?.isVisible; if (componentExposedVisibility === false) return false; let visibility; if (isArray(component)) { @@ -422,6 +424,10 @@ export default function Grid({ gridWidth, currentLayout }) { const moveableBox = document.querySelector(`.moveable-control-box`); const showConfigHandle = (e) => { const targetId = e.target.offsetParent.getAttribute('target-id'); + const componentType = getComponentTypeFromId(targetId); + if (componentType === 'ModuleContainer') { + return; + } useStore.getState().setHoveredComponentBoundaryId(targetId); }; const hideConfigHandle = () => { @@ -461,9 +467,7 @@ export default function Grid({ gridWidth, currentLayout }) { widgetId = widgetId.split('-').slice(0, -1).join('-'); widgetType = boxList.find(({ id }) => id === widgetId)?.component?.component; } - if ( - !['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'Listview', 'Container', 'Table'].includes(widgetType) - ) { + if (!DROPPABLE_PARENTS.has(widgetType)) { isDroppable = false; } } @@ -477,10 +481,15 @@ export default function Grid({ gridWidth, currentLayout }) { .map(({ component }) => component.component); const parentId = draggedOverElemId?.length > 36 ? draggedOverElemId.slice(0, 36) : draggedOverElemId; const parentWidgetType = getComponentTypeFromId(parentId); - const restrictedWidgetsTobeDropped = + let restrictedWidgetsTobeDropped = RESTRICTED_WIDGETS_CONFIG?.[parentWidgetType]?.filter((widgetType) => widgetsTypeToBeDropped.includes(widgetType) ) || []; + + if (isModuleEditor && parentId === undefined) { + restrictedWidgetsTobeDropped = widgetsTypeToBeDropped; + // useGridStore.getState().actions.setIsGroupHandleHoverd(false); + } const isParentChangeAllowed = isEmpty(restrictedWidgetsTobeDropped); if (!isParentChangeAllowed) { @@ -505,7 +514,12 @@ export default function Grid({ gridWidth, currentLayout }) { }); // Show error message - toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`); + if (isModuleEditor) { + // Added this to hide configHandle when multiple components were dragged using the configHandle and placed outside the module + setSelectedComponents([]); + } else { + toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`); + } } const parentElm = draggedOverElem || document.getElementById('real-canvas'); @@ -588,11 +602,11 @@ export default function Grid({ gridWidth, currentLayout }) { keepRatio={false} individualGroupableProps={individualGroupableProps} onResize={(e) => { - if(resizingComponentId !== e.target.id) { + if (resizingComponentId !== e.target.id) { useGridStore.getState().actions.setResizingComponentId(e.target.id); showGridLines(); } - + const currentWidget = boxList.find(({ id }) => id === e.target.id); let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth; if (currentWidget.component?.parent) { @@ -874,7 +888,7 @@ export default function Grid({ gridWidth, currentLayout }) { if (!e.lastEvent) return; // Build the drag context from the event - const dragContext = dragContextBuilder({ event: e, widgets: boxList }); + const dragContext = dragContextBuilder({ event: e, widgets: boxList, isModuleEditor }); const { target, source, dragged } = dragContext; const targetSlotId = target?.slotId; @@ -967,6 +981,19 @@ export default function Grid({ gridWidth, currentLayout }) { left: modalRect.left - mainRect.left, }; setCanvasBounds({ ...relativePosition }); + } else if (isModuleEditor) { + const moduleContainer = e.target.closest('.module-container-canvas'); + const mainCanvas = document.getElementById('real-canvas'); + + const mainRect = mainCanvas.getBoundingClientRect(); + const modalRect = moduleContainer.getBoundingClientRect(); + const relativePosition = { + top: modalRect.top - mainRect.top, + right: mainRect.right - modalRect.right + moduleContainer.offsetWidth, + bottom: modalRect.height + (modalRect.top - mainRect.top), + left: modalRect.left - mainRect.left, + }; + setCanvasBounds({ ...relativePosition }); } // This block is to show grid lines on the canvas when the dragged element is over a new canvas diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js index da5a8341bf..0c78f8730a 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js @@ -54,6 +54,7 @@ import { RESTRICTED_WIDGETS_CONFIG, RESTRICTED_WIDGET_SLOTS_CONFIG, } from '@/AppBuilder/WidgetManager/configs/restrictedWidgetsConfig'; +import { DROPPABLE_PARENTS } from '../../appCanvasConstants'; const CANVAS_ID = 'canvas'; const REAL_CANVAS_ID = 'real-canvas'; @@ -84,8 +85,6 @@ export class DragEntity { * This class helps determine if a slot is valid and handles various properties like modals. */ export class DropAreaEntity { - static dropAreaWidgets = ['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'ModalV2', 'Listview', 'Container', 'Table']; - constructor(widget, slotId) { this.widget = widget; // The widget that owns this slot this.id = widget?.id || CANVAS_ID; // ID of the widget @@ -119,7 +118,7 @@ export class DropAreaEntity { // Determines if the slot is a valid drop target get isDroppable() { - return DropAreaEntity.dropAreaWidgets.includes(this.widgetType); + return DROPPABLE_PARENTS.has(this.widgetType); } // Returns the type of slot (header, footer, body, etc.) @@ -143,7 +142,7 @@ export class DropAreaEntity { * - Any restrictions based on parent-child relationships */ export class DragContext { - constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets }) { + constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets, isModuleEditor = false }) { const sourceWidgetId = sourceSlotId?.slice(0, 36); const sourceWidget = getWidgetById(widgets, sourceWidgetId); @@ -156,6 +155,7 @@ export class DragContext { this.target = new DropAreaEntity(targetWidget, targetSlotId); this.dragged = new DragEntity(draggedWidget); this.widgets = widgets; + this.isModuleEditor = isModuleEditor; } /** @@ -168,7 +168,13 @@ export class DragContext { } get isDroppable() { - const { dragged, target } = this; + const { dragged, target, isModuleEditor } = this; + + // If the target is the canvas and we are in module editor, + // then we don't want to drop the widget outside the module + if (isModuleEditor && target.id === 'canvas') { + return false; + } const restrictedWidgetsOnTarget = RESTRICTED_WIDGETS_CONFIG?.[target.widgetType] || []; const restrictedWidgetsOnTargetSlot = RESTRICTED_WIDGET_SLOTS_CONFIG?.[target.slotType] || []; @@ -181,13 +187,19 @@ export class DragContext { /** * Constructs the **dragging context** by gathering all relevant details from the event. */ -export function dragContextBuilder({ event, widgets }) { +export function dragContextBuilder({ event, widgets, isModuleEditor = false }) { const draggedWidgetId = event.target.id; const draggedWidget = getWidgetById(widgets, draggedWidgetId); const sourceSlotId = draggedWidget.parent; // Initialize drag context - const context = new DragContext({ widgets, draggedWidgetId, sourceSlotId, targetSlotId: sourceSlotId }); + const context = new DragContext({ + widgets, + draggedWidgetId, + sourceSlotId, + targetSlotId: sourceSlotId, + isModuleEditor, + }); // Determine the potential drop target const targetSlotId = getDroppableSlotIdOnScreen(event, widgets); @@ -209,7 +221,7 @@ export const getDroppableSlotIdOnScreen = (event, widgets) => { .map((ele) => extractSlotId(ele)) .filter((slotId) => { const widgetType = getWidgetById(widgets, slotId.slice(0, 36))?.component?.component || CANVAS_ID; - return DropAreaEntity.dropAreaWidgets.includes(widgetType); + return DROPPABLE_PARENTS.has(widgetType); }); return slotId; diff --git a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx index 1aa54dfc7b..018383c61f 100644 --- a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx +++ b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx @@ -4,8 +4,10 @@ import useStore from '@/AppBuilder/_stores/store'; import { pasteComponents, copyComponents } from './appCanvasUtils'; import useKeyHooks from '@/_hooks/useKeyHooks'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }) => { + const { isModuleEditor } = useModuleContext(); const canvasRef = useRef(null); const focusedParentId = useStore((state) => state.focusedParentId, shallow); const handleUndo = useStore((state) => state.handleUndo); @@ -18,10 +20,13 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow); const containerChildrenMapping = useStore((state) => state.containerChildrenMapping, shallow); + const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); + useHotkeys('meta+z, control+z', handleUndo, { enabled: mode === 'edit' }); useHotkeys('meta+shift+z, control+shift+z', handleRedo, { enabled: mode === 'edit' }); const paste = async () => { + if (isModuleEditor && !focusedParentId) return; if (navigator.clipboard && typeof navigator.clipboard.readText === 'function') { try { const cliptext = await navigator.clipboard.readText(); @@ -61,6 +66,24 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } enableReleasedVersionPopupState(); return; } + + // Disable cut, copy, paste, delete shortcuts in module editor + // or when a ModuleContainer is selected + if (isModuleEditor) { + const selectedComponents = getSelectedComponents(); + if ( + selectedComponents.length > 0 && + selectedComponents.some((id) => { + const componentType = getComponentTypeFromId(id, 'canvas'); + return componentType === 'ModuleContainer'; + }) + ) { + if (['KeyC', 'KeyX', 'KeyV', 'KeyD', 'Backspace'].includes(key)) { + return; + } + } + } + switch (key) { case 'Escape': handleEscapeKeyPress(); // clears the selected components diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index 6610ae5fb4..b893c80552 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -7,6 +7,7 @@ import { renderTooltip } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; import ErrorBoundary from '@/_ui/ErrorBoundary'; import { BOX_PADDING } from './appCanvasConstants'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY = [ 'Table', @@ -47,23 +48,27 @@ const RenderWidget = ({ inCanvas = false, darkMode, }) => { - const componentDefinition = useStore((state) => state.getComponentDefinition(id), shallow); + const { moduleId } = useModuleContext(); + const componentDefinition = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const getDefaultStyles = useStore((state) => state.debugger.getDefaultStyles, shallow); const component = componentDefinition?.component; const componentName = component?.name; const [key, setKey] = useState(Math.random()); const resolvedProperties = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.properties, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.properties, + shallow + ); + const resolvedStyles = useStore( + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.styles, shallow ); - const resolvedStyles = useStore((state) => state.getResolvedComponent(id, subContainerIndex)?.styles, shallow); const fireEvent = useStore((state) => state.eventsSlice.fireEvent, shallow); const resolvedGeneralProperties = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.general, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.general, shallow ); const resolvedGeneralStyles = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.generalStyles, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.generalStyles, shallow ); const unResolvedValidation = componentDefinition?.component?.definition?.validation || {}; @@ -73,10 +78,13 @@ const RenderWidget = ({ const setExposedValue = useStore((state) => state.setExposedValue, shallow); const setExposedValues = useStore((state) => state.setExposedValues, shallow); const setDefaultExposedValues = useStore((state) => state.setDefaultExposedValues, shallow); - const resolvedValidation = useStore((state) => state.getResolvedComponent(id)?.validation, shallow); + const resolvedValidation = useStore( + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.validation, + shallow + ); const parentId = component?.parent; const customResolvables = useStore( - (state) => state.resolvedStore.modules.canvas?.customResolvables?.[parentId], + (state) => state.resolvedStore.modules[moduleId]?.customResolvables?.[parentId], shallow ); const { t } = useTranslation(); @@ -110,31 +118,31 @@ const RenderWidget = ({ (key, value) => { // Check if the component is inside the subcontainer and it has its own onOptionChange(setExposedValue) function if (onOptionChange === null) { - setExposedValue(id, key, value); + setExposedValue(id, key, value, moduleId); // Trigger an update when the child components is directly linked to any component - updateDependencyValues(`components.${id}.${key}`); + updateDependencyValues(`components.${id}.${key}`, moduleId); } else { onOptionChange(key, value, id, subContainerIndex); } }, - [id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange] + [id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange, moduleId] ); const setExposedVariables = useCallback( (exposedValues) => { if (onOptionsChange === null) { - setExposedValues(id, 'components', exposedValues); + setExposedValues(id, 'components', exposedValues, moduleId); } else { onOptionsChange(exposedValues, id, subContainerIndex); } }, - [id, setExposedValues, onOptionsChange] + [id, setExposedValues, onOptionsChange, moduleId] ); const fireEventWrapper = useCallback( (eventName, options) => { - fireEvent(eventName, id, 'canvas', customResolvables?.[subContainerIndex] ?? {}, options); + fireEvent(eventName, id, moduleId, customResolvables?.[subContainerIndex] ?? {}, options); return Promise.resolve(); }, - [fireEvent, id, customResolvables, subContainerIndex] + [fireEvent, id, customResolvables, subContainerIndex, moduleId] ); const onComponentClick = useStore((state) => state.eventsSlice.onComponentClickEvent); @@ -155,17 +163,18 @@ const RenderWidget = ({ ? null : ['hover', 'focus'] : !resolvedGeneralProperties?.tooltip?.toString().trim() - ? null - : ['hover', 'focus'] + ? null + : ['hover', 'focus'] } overlay={(props) => renderTooltip({ props, text: inCanvas - ? `${SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component) - ? resolvedProperties?.tooltip - : resolvedGeneralProperties?.tooltip - }` + ? `${ + SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component) + ? resolvedProperties?.tooltip + : resolvedGeneralProperties?.tooltip + }` : `${t(`widget.${component?.name}.description`, component?.description)}`, }) } diff --git a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx index c92cf75a1e..dfa27d3638 100644 --- a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx @@ -5,8 +5,10 @@ import './selecto.scss'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; import { findHighestLevelofSelection } from './Grid/gridUtils'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditorSelecto = () => { + const { moduleId } = useModuleContext(); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); const setSelectedComponents = useStore((state) => state.setSelectedComponents); const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); @@ -16,7 +18,7 @@ export const EditorSelecto = () => { const filterSelectedComponentsByHighestLevel = (selectedIds) => { const highestLevelComponents = findHighestLevelofSelection( selectedIds.map((id) => { - const component = getComponentDefinition(id); + const component = getComponentDefinition(id, moduleId); return { ...component, id, diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index df78a8afa4..9f91c2d2c5 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -6,6 +6,8 @@ import { ConfigHandle } from './ConfigHandle/ConfigHandle'; import { useGridStore } from '@/_stores/gridStore'; import cx from 'classnames'; import RenderWidget from './RenderWidget'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; +import { NO_OF_GRIDS } from './appCanvasConstants'; const WidgetWrapper = memo( ({ @@ -20,24 +22,31 @@ const WidgetWrapper = memo( mode, darkMode, }) => { + const { moduleId } = useModuleContext(); const calculateMoveableBoxHeightWithId = useStore((state) => state.calculateMoveableBoxHeightWithId, shallow); const stylesDefinition = useStore( - (state) => state.getComponentDefinition(id)?.component?.definition?.styles, + (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, + shallow + ); + const layoutData = useStore( + (state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout], shallow ); - const layoutData = useStore((state) => state.getComponentDefinition(id)?.layouts?.[currentLayout], shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); - const componentType = useStore((state) => state.getComponentDefinition(id)?.component?.component, shallow); + const componentType = useStore( + (state) => state.getComponentDefinition(id, moduleId)?.component?.component, + shallow + ); const setHoveredComponentForGrid = useStore((state) => state.setHoveredComponentForGrid, shallow); const canShowInCurrentLayout = useStore((state) => { - const others = state.getResolvedComponent(id, subContainerIndex)?.others; + const others = state.getResolvedComponent(id, subContainerIndex, moduleId)?.others; return others?.[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop']; }); const visibility = useStore((state) => { - const component = state.getResolvedComponent(id, subContainerIndex); - const componentExposedVisibility = state.getExposedValueOfComponent(id)?.isVisible; + const component = state.getResolvedComponent(id, subContainerIndex, moduleId); + const componentExposedVisibility = state.getExposedValueOfComponent(id, moduleId)?.isVisible; if (componentExposedVisibility === false) return false; if (component?.properties?.visibility === false || component?.styles?.visibility === false) return false; return true; @@ -47,16 +56,24 @@ const WidgetWrapper = memo( return null; } - const width = gridWidth * layoutData?.width; + let newLayoutData = layoutData; + + if (componentType === 'ModuleContainer' && mode === 'view') { + newLayoutData = { ...layoutData, top: 0, left: 0, width: NO_OF_GRIDS }; + } + + const width = gridWidth * newLayoutData?.width; const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition); const styles = { width: width + 'px', height: visibility === false ? '10px' : `${height}px`, - transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`, + transform: `translate(${newLayoutData.left * gridWidth}px, ${newLayoutData.top}px)`, WebkitFontSmoothing: 'antialiased', border: visibility === false && mode === 'edit' ? `1px solid var(--border-default)` : 'none', }; + const isModuleContainer = componentType === 'ModuleContainer'; + if (!componentType) return null; return ( <> @@ -67,6 +84,7 @@ const WidgetWrapper = memo( 'position-absolute': readOnly, 'active-target': isWidgetActive, 'opacity-0': isDragging || isResizing, + 'module-container': isModuleContainer, })} data-id={`${id}`} id={id} @@ -76,30 +94,32 @@ const WidgetWrapper = memo( // zIndex: mode === 'view' && widget.component.component == 'Datepicker' ? 2 : null, ...styles, }} - onMouseEnter={(e) => { - if (isDragging) return; + onMouseEnter={() => { + if (isDragging || isModuleContainer) return; setHoveredComponentForGrid(id); }} onMouseLeave={() => { - if (isDragging) return; + if (isDragging || isModuleContainer) return; setHoveredComponentForGrid(''); }} > {mode == 'edit' && ( )} { +export const addNewWidgetToTheEditor = ( + componentType, + eventMonitorObject, + currentLayout, + realCanvasRef, + parentId, + moduleInfo = undefined +) => { const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect(); const componentMeta = componentTypes.find((component) => component.component === componentType); const componentName = computeComponentName(componentType, useStore.getState().getCurrentPageComponents()); @@ -51,6 +58,24 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre const mainCanvasWidth = useGridStore.getState().subContainerWidths['canvas']; let width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth); + let customLayouts = undefined; + + if (moduleInfo) { + componentData.definition.properties.moduleAppId = { value: moduleInfo.moduleId }; + componentData.definition.properties.moduleVersionId = { value: moduleInfo.versionId }; + componentData.definition.properties.moduleEnvironmentId = { value: moduleInfo.environmentId }; + componentData.definition.properties.visibility = { value: true }; + customLayouts = moduleInfo.moduleContainer.layouts; + + const inputItems = Object.values( + moduleInfo.moduleContainer.component.definition.properties?.input_items?.value ?? {} + ); + + for (const { name, default_value } of inputItems) { + componentData.definition.properties[name] = { value: default_value }; + } + } + // Ensure minimum width width = Math.max(width, 1); @@ -76,14 +101,14 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre [currentLayout]: { top: top, left: left, - width, - height: defaultHeight, + width: customLayouts ? customLayouts[currentLayout].width : width, + height: customLayouts ? customLayouts[currentLayout].height : defaultHeight, }, [nonActiveLayout]: { top: top, left: left, - width, - height: defaultHeight, + width: customLayouts ? customLayouts[nonActiveLayout].width : width, + height: customLayouts ? customLayouts[nonActiveLayout].height : defaultHeight, }, }, withDefaultChildren: WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentData.component), @@ -170,6 +195,7 @@ export function addChildrenWidgetsToParent(componentType, parentId, currentLayou component: { ...componentData, parent: _parent, + name: widgetName, }, layouts: { [currentLayout]: { @@ -682,10 +708,14 @@ export function pasteComponents(targetParentId, copiedComponentObj) { toast.success(`Component${filteredComponentsCount > 1 ? 's' : ''} pasted successfully`); } -export const getCanvasWidth = (currentLayout) => { - if (currentLayout === 'mobile') { - return CANVAS_WIDTHS.deviceWindowWidth; +export const getCanvasWidth = (moduleId = 'canvas') => { + if (moduleId !== 'canvas') { + return '100%'; } + + // if (currentLayout === 'mobile') { + // return CANVAS_WIDTHS.deviceWindowWidth; + // } const windowWidth = window.innerWidth; const widthInPx = windowWidth - (CANVAS_WIDTHS.leftSideBarWidth + CANVAS_WIDTHS.rightSideBarWidth); const canvasMaxWidth = useStore.getState().globalSettings.canvasMaxWidth; diff --git a/frontend/src/AppBuilder/AppCanvas/selecto.scss b/frontend/src/AppBuilder/AppCanvas/selecto.scss index 7366e2fd06..0962255be8 100644 --- a/frontend/src/AppBuilder/AppCanvas/selecto.scss +++ b/frontend/src/AppBuilder/AppCanvas/selecto.scss @@ -1,20 +1,21 @@ .active-target { outline: 1px solid #4af !important; -} +} -.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { +.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { outline: 1px solid #4af; z-index: 4 !important; } -.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { +.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { // outline: 1px solid #4af; z-index: 4 !important; } +.main-editor-canvas .widget-target.module-container { + outline: dotted 2px #CCD1D5 !important; +} + // .main-editor-canvas .widget-target:hover { // outline: 1px solid #4af; -// } - - - +// } \ No newline at end of file diff --git a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js index 969e60f398..d80cbd76cc 100644 --- a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js +++ b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js @@ -3,11 +3,13 @@ import { isEmpty } from 'lodash'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { LEFT_SIDEBAR_WIDTH } from './appCanvasConstants'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const useSidebarMargin = (canvasContainerRef) => { + const { moduleId } = useModuleContext(); const [editorMarginLeft, setEditorMarginLeft] = useState(0); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); useEffect(() => { if (mode !== 'view') setEditorMarginLeft(isSidebarOpen ? LEFT_SIDEBAR_WIDTH : 0); diff --git a/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx b/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx new file mode 100644 index 0000000000..f182a5cbb9 --- /dev/null +++ b/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx @@ -0,0 +1,100 @@ +import React, { useCallback, useMemo } from 'react'; +import SelectComponent from '@/_ui/Select'; +import { components } from 'react-select'; +import Check from '@/_ui/Icon/solidIcons/Check'; +import useStore from '@/AppBuilder/_stores/store'; + +const Option = (props) => { + return ( + +
+ {props.label} + {props.isSelected && ( + + + + )} +
+
+ ); +}; + +const selectCustomStyles = { + control: (base, state) => { + return { + ...base, + border: state.isFocused ? '1px solid #3E63DD' : '1px solid #cccccc', + boxShadow: state.isFocused ? '0px 0px 6px #3E63DD' : 'none', + backgroundColor: state.isFocused ? 'var(--indigo2)' : 'var(--base)', + '&:hover': { + border: '1px solid #3E63DD !important', + boxShadow: '0px 0px 6px #3E63DD', + }, + borderRadius: '6px', + width: '144px', + minHeight: '32px', + }; + }, + + dropdownIndicator: (base) => ({ + ...base, + padding: '4px', + }), + menuList: (base) => ({ + ...base, + padding: '4px', + }), + option: (base, state) => ({ + ...base, + backgroundColor: state.isFocused ? '#F0F4FF !important' : 'white', + color: '#11181C', + borderRadius: '6px', + }), +}; + +export const Query = ({ value, onChange, meta }) => { + const dataQueries = useStore((state) => state.dataQuery.getCurrentModuleQueries('canvas')); + const options = dataQueries + .filter((query) => !(meta?.skipKinds ?? []).includes(query.kind)) + .map((query) => ({ name: query.name, value: query.id })); + + const onValueChange = useCallback( + (value) => { + console.log('value--- ', value, options); + onChange(value); + }, + [onChange, options] + ); + + // const cleanedValue = useMemo(() => { + // if (initialValue) { + // return initialValue.replace('{{queries.', '').replace('}}', ''); + // } + // return ''; + // }, [initialValue]); + + return ( +
+
e.stopPropagation()}> + null, + Option, + }} + /> +
+
+ ); +}; diff --git a/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js b/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js index eb01df1241..a249f52676 100644 --- a/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js +++ b/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js @@ -20,4 +20,5 @@ export const TypeMapping = { visibility: 'Visibility', numberInput: 'NumberInput', tableRowHeightInput: 'TableRowHeightInput', + query: 'Query', }; diff --git a/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx b/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx index 9a204b9cf2..99d80a6050 100644 --- a/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx +++ b/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx @@ -18,6 +18,7 @@ import { NumberInput } from '../CodeBuilder/Elements/NumberInput'; import { Datepicker } from '../CodeBuilder/Elements/Datepicker'; import TableRowHeightInput from '../CodeBuilder/Elements/TableRowHeightInput'; import { TimePicker } from '../CodeBuilder/Elements/TimePicker'; +import { Query } from '../CodeBuilder/Elements/Query'; import { ColorSwatches } from '@/modules/Appbuilder/components'; const AllElements = { @@ -40,6 +41,7 @@ const AllElements = { TableRowHeightInput, Datepicker, TimePicker, + Query, }; export const DynamicFxTypeRenderer = ({ paramType, ...restProps }) => { diff --git a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx index c123cb2b9c..5c422b1eb3 100644 --- a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx +++ b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx @@ -13,6 +13,7 @@ import { reservedKeywordReplacer } from '@/_lib/reserved-keyword-replacer'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { Overlay } from 'react-bootstrap'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const sanitizeLargeDataset = (data, callback) => { const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes @@ -90,11 +91,12 @@ export const PreviewBox = ({ isWorkspaceVariable, validationFn, }) => { + const { moduleId } = useModuleContext(); const [resolvedValue, setResolvedValue] = useState(''); const [error, setError] = useState(null); const [coersionData, setCoersionData] = useState(null); const [largeDataset, setLargeDataset] = useState(false); - const globals = useStore((state) => state.getAllExposedValues().constants || {}, shallow); + const globals = useStore((state) => state.getAllExposedValues(moduleId).constants || {}, shallow); const secrets = useStore((state) => state.getSecrets(), shallow); const globalServerConstantsRegex = /^\{\{.*globals\.server.*\}\}$/; diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 16514ca409..9c85e0bf43 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -28,11 +28,13 @@ import CodeHinter from './CodeHinter'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext'; import { createReferencesLookup } from '@/_stores/utils'; import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { + const { moduleId } = useModuleContext(); const { initialValue, onChange, enablePreview = true, portalProps } = restProps; const { validation = {} } = fieldMeta; const [showPreview, setShowPreview] = useState(false); @@ -42,7 +44,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r const [cursorInsidePreview, setCursorInsidePreview] = useState(false); const [showSuggestions, setShowSuggestions] = useState(true); const validationFn = restProps?.validationFn; - const componentDefinition = useStore((state) => state.getComponentDefinition(componentId), shallow); + const componentDefinition = useStore((state) => state.getComponentDefinition(componentId, moduleId), shallow); const parentId = componentDefinition?.component?.parent; const customResolvables = useStore((state) => state.resolvedStore.modules.canvas?.customResolvables, shallow); @@ -522,6 +524,32 @@ const DynamicEditorBridge = (props) => { setForceCodeBox(fxActive); }, [component, fxActive]); + const renderFx = () => { + if (paramType === 'query' || !(paramLabel !== 'Type' && isFxNotRequired === undefined)) { + return null; + } + return ( +
+ { + if (codeShow) { + setForceCodeBox(false); + onFxPress(false); + } else { + setForceCodeBox(true); + onFxPress(true); + } + }} + dataCy={cyLabel} + /> +
+ ); + }; + const fxClass = isEventManagerParam ? 'justify-content-start' : 'justify-content-end'; return (
@@ -538,26 +566,7 @@ const DynamicEditorBridge = (props) => { )}
- {paramLabel !== 'Type' && isFxNotRequired === undefined && ( -
- { - if (codeShow) { - setForceCodeBox(false); - onFxPress(false); - } else { - setForceCodeBox(true); - onFxPress(true); - } - }} - dataCy={cyLabel} - /> -
- )} + {renderFx()}
{!codeShow && ( diff --git a/frontend/src/AppBuilder/CodeEditor/utils.js b/frontend/src/AppBuilder/CodeEditor/utils.js index 11d6eb3c90..1b5fe0aafb 100644 --- a/frontend/src/AppBuilder/CodeEditor/utils.js +++ b/frontend/src/AppBuilder/CodeEditor/utils.js @@ -154,6 +154,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor 'queries', 'globals', 'page', + 'input', 'constants', 'moment', '_', @@ -168,6 +169,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor isJsCode ? state?.queries : undefined, isJsCode ? state?.globals : undefined, isJsCode ? state?.page : undefined, + isJsCode ? state?.input : undefined, state?.constants, // Passing constants as an argument allows the evaluated code to access and utilize the constants value correctly. moment, _, @@ -365,6 +367,7 @@ export const FxParamTypeMapping = Object.freeze({ visibility: 'Visibility', numberInput: 'NumberInput', tableRowHeightInput: 'TableRowHeightInput', + query: 'Query', }); export function computeCoercion(oldValue, newValue) { diff --git a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx index 05009fd531..56070684c0 100644 --- a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx +++ b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx @@ -6,6 +6,7 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { decodeEntities } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const appVersionLoadingStatus = Object.freeze({ loading: 'loading', @@ -14,6 +15,7 @@ const appVersionLoadingStatus = Object.freeze({ }); export const AppVersionsManager = function ({ darkMode }) { + const { moduleId } = useModuleContext(); const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading); const [deleteVersion, setDeleteVersion] = useState({ @@ -54,15 +56,15 @@ export const AppVersionsManager = function ({ darkMode }) { deleteVersionAction: state.deleteVersionAction, selectedEnvironment: state.selectedEnvironment, currentLayout: state.currentLayout, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, releasedVersionId: state.releasedVersionId, editingVersion: state.editingVersion, setCurrentVersionId: state.setCurrentVersionId, currentVersionId: state.currentVersionId, - creationMode: state.app.creationMode, - isPublic: state.app.isPublic, - isViewer: state.isViewer, - currentMode: state.currentMode, + creationMode: state.appStore.modules[moduleId].app.creationMode, + isPublic: state.appStore.modules[moduleId].app.isPublic, + isViewer: state.appStore.modules[moduleId].isViewer, + currentMode: state.modeStore.modules[moduleId].currentMode, }), shallow ); diff --git a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx index 3add0e6074..e23fc51e9a 100644 --- a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import Select from '@/_ui/Select'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CreateVersionModal = ({ showCreateAppVersion, @@ -17,6 +18,7 @@ const CreateVersionModal = ({ fetchingOrgGit, handleCommitOnVersionCreation = () => { }, }) => { + const { moduleId } = useModuleContext(); const [isCreatingVersion, setIsCreatingVersion] = useState(false); const [versionName, setVersionName] = useState(''); @@ -35,7 +37,7 @@ const CreateVersionModal = ({ developmentVersions: state.developmentVersions, featureAccess: state.license.featureAccess, editingVersion: state.currentVersionId, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, currentVersionId: state.currentVersionId, setCurrentVersionId: state.setCurrentVersionId, selectedVersion: state.selectedVersion, diff --git a/frontend/src/AppBuilder/Header/EditAppName.jsx b/frontend/src/AppBuilder/Header/EditAppName.jsx index 7211bb085b..e9dff6074a 100644 --- a/frontend/src/AppBuilder/Header/EditAppName.jsx +++ b/frontend/src/AppBuilder/Header/EditAppName.jsx @@ -8,10 +8,17 @@ import { InfoOrErrorBox } from './InfoOrErrorBox'; import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; function EditAppName() { + const { moduleId } = useModuleContext(); const [appId, appName, setAppName, appCreationMode] = useStore( - (state) => [state.app.appId, state.app.appName, state.setAppName, state.app.creationMode], + (state) => [ + state.appStore.modules[moduleId].app.appId, + state.appStore.modules[moduleId].app.appName, + state.setAppName, + state.appStore.modules[moduleId].app.creationMode, + ], shallow ); diff --git a/frontend/src/AppBuilder/Header/EditVersionModal.jsx b/frontend/src/AppBuilder/Header/EditVersionModal.jsx index 3a126c6285..c22dd74991 100644 --- a/frontend/src/AppBuilder/Header/EditVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/EditVersionModal.jsx @@ -4,8 +4,10 @@ import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) => { + const { moduleId } = useModuleContext(); const [isEditingVersion, setIsEditingVersion] = useState(false); const { updateVersionNameAction, @@ -15,7 +17,7 @@ export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) (state) => ({ updateVersionNameAction: state.updateVersionNameAction, selectedVersion: state.selectedVersion, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, }), shallow ); diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index fb779c6269..5e1e639819 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -12,12 +12,15 @@ import RightTopHeaderButtons from './RightTopHeaderButtons/RightTopHeaderButtons import BuildSuggestions from './BuildSuggestions'; import GitSyncManager from './GitSyncManager'; import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; +import { ModuleEditorBanner } from '@/modules/Modules/components'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditorHeader = ({ darkMode }) => { + const { moduleId, isModuleEditor } = useModuleContext(); const { isSaving, saveError, isVersionReleased } = useStore( (state) => ({ - isSaving: state.app.isSaving, - saveError: state.app.saveError, + isSaving: state.appStore.modules[moduleId].app.isSaving, + saveError: state.appStore.modules[moduleId].app.saveError, isVersionReleased: state.isVersionReleased, }), shallow @@ -72,7 +75,10 @@ export const EditorHeader = ({ darkMode }) => { }} >
- +
+ {isModuleEditor && } + +
@@ -96,20 +102,21 @@ export const EditorHeader = ({ darkMode }) => { {shouldEnableMultiplayer && }
-
- {/*
*/} + {!isModuleEditor &&
}
- {/* {editingVersion && ( */} - - {/* )} */} - - + {!isModuleEditor && ( + <> + + + + + )}
- + diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx index 494eaa52e9..48b77cc238 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx @@ -8,8 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { + const { moduleId } = useModuleContext(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( @@ -19,7 +21,7 @@ const ReleaseVersionButton = function DeployVersionButton() { editingVersion: state.editingVersion, isEditorFreezed: state.isEditorFreezed, updateReleasedVersionId: state.updateReleasedVersionId, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, versionToBeReleased: state.currentVersionId, // selectedVersionId: state.selectedVersion.id, }), diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx index d22a2978c8..9b144bdd88 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx @@ -8,19 +8,21 @@ import { isEmpty } from 'lodash'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; import { PromoteReleaseButton } from '@/modules/Appbuilder/components'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -const RightTopHeaderButtons = () => { +const RightTopHeaderButtons = ({ isModuleEditor }) => { return (
- + {!isModuleEditor && }
); }; const PreviewAndShareIcons = () => { + const { moduleId } = useModuleContext(); const { featureAccess, currentPageHandle, @@ -36,14 +38,14 @@ const PreviewAndShareIcons = () => { } = useStore( (state) => ({ featureAccess: state.license?.featureAccess, - currentPageHandle: state?.currentPageHandle, + currentPageHandle: state?.modules[moduleId].currentPageHandle, selectedEnvironment: state.selectedEnvironment, isVersionReleased: state.releasedVersionId === state.selectedVersion?.id, editingVersion: state.editingVersion, - appId: state.app.appId, - app: state.app.app, - slug: state.app.slug, - isPublic: state.app.isPublic, + appId: state.appStore.modules[moduleId].app.appId, + app: state.appStore.modules[moduleId].app.app, + slug: state.appStore.modules[moduleId].app.slug, + isPublic: state.appStore.modules[moduleId].app.isPublic, currentVersionId: state.currentVersionId, selectedVersion: state.selectedVersion, }), diff --git a/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx b/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx index 7b775e2b08..3823e2bfff 100644 --- a/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx @@ -10,7 +10,7 @@ function Debugger({ pinned, setPinned }) { shallow ); - const currentPageId = useStore((state) => state.currentPageId); + const currentPageId = useStore((state) => state.modules.canvas.currentPageId); const logsToBeShown = logs.filter((log) => log.page === currentPageId); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx index a70799ea54..0bce2fb999 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx @@ -3,12 +3,14 @@ import { Button } from '@/components/ui/Button/Button'; import ExportAppModal from '@/HomePage/ExportAppModal'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import cx from 'classnames'; const AppExport = ({ darkMode }) => { + const { moduleId } = useModuleContext(); const { app } = useStore( (state) => ({ - app: state.app, + app: state.appStore.modules[moduleId].app, }), shallow ); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx index 9f021cd070..209677b747 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx @@ -11,12 +11,14 @@ import FxButton from '@/Editor/CodeBuilder/Elements/FxButton'; import { useTranslation } from 'react-i18next'; import { Confirm } from '@/Editor/Viewer/Confirm'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CanvasSettings = ({ darkMode }) => { + const { moduleId } = useModuleContext(); const { globalSettings, globalSettingsChanged, resolveOthers, getCanvasBackgroundColor } = useStore( (state) => ({ globalSettings: state.globalSettings, - updateGlobalSettings: state.updateGlobalSettings, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, globalSettingsChanged: state.globalSettingsChanged, resolveOthers: state.resolveOthers, getCanvasBackgroundColor: state.getCanvasBackgroundColor, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx index c2b969146f..8b1b1cece0 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx @@ -3,12 +3,14 @@ import useStore from '@/AppBuilder/_stores/store'; import SwitchComponent from '@/components/ui/Switch/Index'; import { shallow } from 'zustand/shallow'; import { Confirm } from '@/Editor/Viewer/Confirm'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const MaintenanceMode = ({ darkMode }) => { + const { moduleId } = useModuleContext(); const [showConfirmation, setConfirmationShow] = useState(false); const { isMaintenanceOn, toggleAppMaintenance } = useStore( (state) => ({ - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, toggleAppMaintenance: state.toggleAppMaintenance, }), shallow diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx index c996b09005..e7f255d697 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx @@ -8,9 +8,11 @@ import { getHostURL, replaceEditorURL } from '@/_helpers/routes'; import useStore from '@/AppBuilder/_stores/store'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; // import { useStore } from '@/store'; const SlugInput = () => { + const { moduleId } = useModuleContext(); const { slug: oldSlug, appId, @@ -20,11 +22,11 @@ const SlugInput = () => { } = useStore( (state) => ({ globalSettings: state.globalSettings, - slug: state.app.slug, - appId: state.app.appId, - app: state.app, + slug: state.appStore.modules[moduleId].app.slug, + appId: state.appStore.modules[moduleId].app.appId, + app: state.appStore.modules[moduleId].app, setApp: state.setApp, - currentPage: state.modules.canvas.pages[state.currentPageId], + currentPage: state.modules[moduleId].pages[state.modules[moduleId].currentPageIndex], }), shallow ); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index aaf12714b9..e2b5947206 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -10,6 +10,7 @@ import LeftSidebarInspector from './LeftSidebarInspector/LeftSidebarInspector'; import GlobalSettings from './GlobalSettings'; import '../../_styles/left-sidebar.scss'; import Debugger from './Debugger/Debugger'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; // TODO: remove passing refs to LeftSidebarItem and use state @@ -23,6 +24,7 @@ export const BaseLeftSidebar = ({ renderAISideBarTrigger = () => null, renderAIChat = () => null, }) => { + const { moduleId, isModuleEditor, appType } = useModuleContext(); const [ pinned, selectedSidebarItem, @@ -41,7 +43,7 @@ export const BaseLeftSidebar = ({ state.selectedSidebarItem, state.setIsLeftSideBarPinned, state.setSelectedSidebarItem, - state.currentMode, + state.modeStore.modules[moduleId].currentMode, state.queryPanel.queryPanelHeight, state.debugger.unreadErrorCount, state.debugger.resetUnreadErrorCount, @@ -104,6 +106,8 @@ export const BaseLeftSidebar = ({ // popoverContentHeight={popoverContentHeight} setPinned={setPinned} pinned={pinned} + moduleId={moduleId} + appType={appType} /> ); case 'tooljetai': @@ -148,6 +152,7 @@ export const BaseLeftSidebar = ({ // globalSettingsChanged={globalSettingsChanged} // globalSettings={appDefinition.globalSettings} darkMode={darkMode} + isModuleEditor={isModuleEditor} // toggleAppMaintenance={toggleAppMaintenance} // isMaintenanceOn={isMaintenanceOn} // app={app} @@ -162,72 +167,79 @@ export const BaseLeftSidebar = ({ return null; } + const renderCommonItems = () => { + return ( + <> + handleSelectedSidebarItem('inspect')} + darkMode={darkMode} + icon="inspect" + className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`} + tip="Inspector" + ref={setSideBarBtnRefs('inspect')} + /> + + handleSelectedSidebarItem('debugger')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + count={unreadErrorCount} + tip="Debugger" + ref={setSideBarBtnRefs('debugger')} + /> + + ); + }; + + const renderLeftSidebarItems = () => { + if (isModuleEditor) { + return renderCommonItems(); + } + return ( + <> + {renderAISideBarTrigger({ + selectedSidebarItem: selectedSidebarItem, + onClick: () => handleSelectedSidebarItem('tooljetai'), + darkMode: darkMode, + icon: 'tooljetai', + className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`, + tip: 'Build with AI', + ref: setSideBarBtnRefs('tooljetai'), + })} + handleSelectedSidebarItem('page')} + darkMode={darkMode} + icon="page" + className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`} + tip="Pages" + ref={setSideBarBtnRefs('page')} + /> + {renderCommonItems()} + handleSelectedSidebarItem('settings')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + tip="Settings" + ref={setSideBarBtnRefs('settings')} + isModuleEditor={isModuleEditor} + /> + + ); + }; + return (
- {renderAISideBarTrigger({ - selectedSidebarItem: selectedSidebarItem, - onClick: () => handleSelectedSidebarItem('tooljetai'), - darkMode: darkMode, - icon: 'tooljetai', - className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`, - tip: 'Build with AI', - ref: setSideBarBtnRefs('tooljetai'), - })} - handleSelectedSidebarItem('page')} - darkMode={darkMode} - icon="page" - className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`} - tip="Pages" - ref={setSideBarBtnRefs('page')} - /> - - handleSelectedSidebarItem('inspect')} - darkMode={darkMode} - icon="inspect" - className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`} - tip="Inspector" - ref={setSideBarBtnRefs('inspect')} - /> - - handleSelectedSidebarItem('debugger')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - count={unreadErrorCount} - tip="Debugger" - ref={setSideBarBtnRefs('debugger')} - /> - handleSelectedSidebarItem('settings')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - tip="Settings" - ref={setSideBarBtnRefs('settings')} - /> - - {/* {dataSources?.length > 0 && ( - handleSelectedSidebarItem('datasource')} - icon="datasource" - className={`left-sidebar-item left-sidebar-layout sidebar-datasources`} - tip="Sources" - ref={setSideBarBtnRefs('datasource')} - /> - )} */} - + {renderLeftSidebarItems()} { // if tooljetai is open don't close @@ -238,7 +250,7 @@ export const BaseLeftSidebar = ({ toggleLeftSidebar(false); }} open={isSidebarOpen} - popoverContentClassName={`p-0 sidebar-h-100-popover ${selectedSidebarItem}`} + popoverContentClassName={`p-0 left-sidebar-scrollbar sidebar-h-100-popover ${selectedSidebarItem}`} side="right" popoverContent={renderPopoverContent()} popoverContentHeight={popoverContentHeight} diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx new file mode 100644 index 0000000000..5c5d673c83 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const ArrayNode = ({ value }) => { + return ( +
+ {`[${value.length}]`} +
+ ); +}; + +export default ArrayNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx new file mode 100644 index 0000000000..65616c4883 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const BooleanNode = ({ value }) => { + return ( +
+ + {value.toString()} + +
+ ); +}; + +export default BooleanNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx new file mode 100644 index 0000000000..858aaa4d8f --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const FunctionNode = () => { + return ( +
+ + function + +
+ ); +}; + +export default FunctionNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx new file mode 100644 index 0000000000..40ff32737a --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const NullNode = ({ value }) => { + return ( +
+ {value === null ? 'null' : 'undefined'} +
+ ); +}; + +export default NullNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx new file mode 100644 index 0000000000..18643844e6 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const NumberNode = ({ value }) => { + return ( +
+ + {value} + +
+ ); +}; + +export default NumberNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx new file mode 100644 index 0000000000..9977d6f108 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; +const ObjectNode = ({ value }) => { + return ( +
+ {`{${Object.keys(value).length}}`} +
+ ); +}; + +export default ObjectNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx new file mode 100644 index 0000000000..94bf363706 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -0,0 +1,155 @@ +import React, { useState } from 'react'; +import StringNode from './StringNode'; +import FunctionNode from './FunctionNode'; +import NumberNode from './NumberNode'; +import BooleanNode from './BooleanNode'; +import NullNode from './NullNode'; +import ArrayNode from './ArrayNode'; +import ObjectNode from './ObjectNode'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import OverflowTooltip from '@/_components/OverflowTooltip'; +import { ToolTip } from '@/_components/ToolTip'; +import { DefaultCopyIcon } from '../../DefaultCopyIcon'; +import { copyToClipboard, extractComponentName } from '../../utils'; +import WidgetIcon from '@/../assets/images/icons/widgets'; +import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers'; + +const renderNodeIcons = (node, iconsList, darkMode) => { + const icon = iconsList.filter((icon) => icon?.iconName === node)[0]; + + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx({ height: 14, width: 14 })}
+
+ ); + } + return icon.jsx({ height: 14, width: 14 }); + } + return null; +}; + +const Row = ({ label, value, level = 1, absolutePath, iconsList, darkMode }) => { + const [isExpanded, setIsExpanded] = useState(false); + const Node = () => { + if (typeof value === 'string') { + return ; + } else if (typeof value === 'undefined' || value === null) { + return ; + } else if (typeof value === 'number') { + return ; + } else if (typeof value === 'boolean') { + return ; + } else if (Array.isArray(value)) { + return ; + } else if (typeof value === 'object') { + return ; + } else if (typeof value === 'function') { + return ; + } + }; + + const isObject = typeof value === 'object' && !Array.isArray(value) && value !== null; + const isArray = Array.isArray(value); + + return ( +
+
+
setIsExpanded((prev) => !prev)}> +
+ {(isArray || isObject) && + (isExpanded ? ( + + ) : ( + + ))} +
+
+ + {renderNodeIcons(label, iconsList, darkMode)} + {label} + +
+
+ +
+
+ + { + copyToClipboard(`{{${absolutePath}}}`, false); + }} + className="copy-to-clipboard json-viewer-action-icon" + > + + + + + { + copyToClipboard(value); + }} + className="json-viewer-action-icon" + > + + + +
+
+
+ {isExpanded && isObject && ( +
+ {Object.entries(value).map(([key, val]) => ( + + ))} +
+ )} + {isExpanded && isArray && ( +
+ {value.map((item, index) => { + return ( + + ); + })} +
+ )} +
+ ); +}; + +export default Row; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx new file mode 100644 index 0000000000..60e3d42d20 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const StringNode = ({ value }) => { + return ( +
+ {`"${value}"`} +
+ ); +}; + +export default StringNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx new file mode 100644 index 0000000000..d37aff84aa --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import Row from './Components/Row'; +import './styles.scss'; + +const CustomJSONViewer = ({ data, absolutePath, iconsList }) => { + let modifiedData = data; + if (typeof data !== 'object') modifiedData = { '': data }; + return ( +
+ {Object.entries(modifiedData).map(([key, value], index) => { + return ( + + ); + })} +
+ ); +}; + +export default CustomJSONViewer; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss new file mode 100644 index 0000000000..eea3660631 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss @@ -0,0 +1,93 @@ +.custom-json-viewer { + margin-left: 16px; + margin-right: 16px; + font-family: "IBM Plex Sans"; + font-size: 12px; + color: var(--text-default, #1B1F24); + overflow-x: auto; + min-width: 0; + + // Hide scrollbar for Chrome, Safari and Opera + &::-webkit-scrollbar { + display: none; + } + + // Hide scrollbar for IE, Edge and Firefox + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + + .json-viewer-row-container { + min-width: max-content; + width: 100%; + position: relative; + &:hover { + background-color: var(--interactive-overlays-fill-hover); + width: 100%; + + .json-viewer-actions-container { + display: flex; + } + } + } + + .json-viewer-row { + width: 100%; + display: flex; + height: 20px; + align-items: center; + overflow: hidden; + cursor: pointer; + + .json-viewer-expand-icon { + width: 12px; + height: 12px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + .json-viewer-label-container { + margin-right: 4px; + flex-shrink: 0; /* don't shrink */ + white-space: nowrap; + } + + .json-viewer-value-container { + flex: 1; /* take available space */ + min-width: 0; /* allow shrinkage */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding-right: 70px; /* Add padding to prevent text from going under actions */ + } + + .json-viewer-actions-container { + display: none; + margin-left: auto; + height: 12px; + width: 40px; + align-items: center; + flex-shrink: 0; + position: absolute; + right: 16px; /* Align with the parent container's margin */ + background: var(--bg-default); /* Add background to ensure text doesn't show through */ + z-index: 1; /* Ensure actions stay on top */ + + .json-viewer-action-icon { + display: flex; + align-items: center; + justify-content: center; + height: 20px; + width: 20px; + + &:hover { + cursor: pointer; + background-color: var(--button-outline-hover); + border-radius: 4px; + } + } + } + + } +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx new file mode 100644 index 0000000000..c71b8ad5c3 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx @@ -0,0 +1,19 @@ +import React from 'react'; + +export const DefaultCopyIcon = ({ height = 12, width = 12, fill = '#6A727C' }) => ( + + + +); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx new file mode 100644 index 0000000000..05c4333549 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -0,0 +1,138 @@ +import React, { useEffect, useState } from 'react'; +import { ToolTip } from '@/_components/ToolTip'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import cx from 'classnames'; +import { DefaultCopyIcon } from './DefaultCopyIcon'; +import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers'; + +export const HiddenOptions = (props) => { + const { nodeSpecificFilteredActions, generalActionsFiltered, darkMode, setActionClicked, data } = props; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const [showMenu, setShowMenu] = useState(false); + const closeMenu = () => { + setShowMenu(false); + }; + + const copyPath = () => { + generalActionsFiltered[0].dispatchAction(`{{${data?.selectedNodePath}}}`, false); + }; + + const copyValue = () => { + const value = getResolvedValue(`{{${data?.selectedNodePath}}}`); + generalActionsFiltered[0].dispatchAction(value); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (event.target.closest('.copy-menu-options') === null) { + closeMenu(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // This is to ensure that the actionClicked state is updated when the menu is shown or deleted on the next render to avoid misplacing the Popover + useEffect(() => { + setTimeout(() => setActionClicked(showMenu), 0); + }, [showMenu]); + + const renderOptions = () => { + return nodeSpecificFilteredActions?.map((actionOption, index) => { + const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; + if (icon) { + return ( +
+ + {/* ${name === 'Go to component' ? '' : currentNode} */} + { + event.stopPropagation(); + dispatchAction(data); + }} + > + + + +
+ ); + } + }); + }; + + return ( +
+ {renderOptions()} + e.stopPropagation()}> + +
+
{ + event.stopPropagation(); + copyPath(); + closeMenu(); + }} + className="option" + data-cy="inspector-copy-path" + > + + Copy path +
+
{ + event.stopPropagation(); + copyValue(); + closeMenu(); + }} + className="option" + data-cy="inspector-copy-value" + > + + Copy value +
+
+
+ + } + > +
{ + event.stopPropagation(); + setShowMenu((prev) => !prev); + }} + className="node-action-icon" + style={{ + outline: 'none', + ...(showMenu && { backgroundColor: 'var(--button-outline-pressed, rgba(136, 144, 153, 0.18)' }), + }} + > + +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx new file mode 100644 index 0000000000..3829f2187a --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -0,0 +1,238 @@ +import React, { useMemo } from 'react'; +import TreeView, { flattenTree } from 'react-accessible-treeview'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import Fuse from 'fuse.js'; +import JSONViewer from './JSONViewer'; +import { Node } from './Node'; +import { v4 as uuidv4 } from 'uuid'; +import InputComponent from '@/components/ui/Input/Index'; +import { isEmpty } from 'lodash'; + +const ensureUniqueIds = (node, parentId = '') => { + if (!node) return node; + + const seenIds = new Set(); + const processNode = (currentNode, currentParentId = '') => { + if (!currentNode) return currentNode; + + const newChildren = currentNode.children?.map((child, index) => { + const baseId = child.id; + let uniqueId = baseId; + let counter = 1; + + while (seenIds.has(uniqueId)) { + uniqueId = `${baseId}_${counter}`; + counter++; + } + + seenIds.add(uniqueId); + return processNode({ ...child, id: uniqueId }, uniqueId); + }); + + return { + ...currentNode, + children: newChildren, + }; + }; + + return processNode(node, parentId); +}; + +const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => { + const searchValue = useStore((state) => state.inspectorSearchValue, shallow); + const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow); + const getComponentDefinition = useStore((state) => state.getComponentDefinition, shallow); + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow); + const selectedNodePath = useStore((state) => state.selectedNodePath, shallow); + const setSelectedNodePath = useStore((state) => state.setSelectedNodePath, shallow); + + const selectedNodes = useStore((state) => state.selectedNodes, shallow); + + function fuzzySearch(query, searchablePaths) { + const list = Array.from(searchablePaths); + const fuse = new Fuse(list, { + threshold: 0.2, + minMatchCharLength: 2, + includeScore: true, + distance: 1000, + tokenize: true, + matchAllTokens: true, + }); + return fuse.search(query).map((result) => result.item); + } + + const [searchedSet, pathSet] = useMemo(() => { + const result = fuzzySearch(searchValue, searchablePaths); + const expandedIdSet = new Set(); + result.forEach((id) => { + const pathArray = id.split('.'); + for (let i = pathArray.length - 1; i > 0; i--) { + const parentPath = pathArray.slice(0, i).join('.'); + if (!expandedIdSet.has(parentPath)) { + expandedIdSet.add(parentPath); + } + } + }); + return [new Set(result), expandedIdSet]; + }, [searchValue, JSON.stringify(searchablePaths)]); + + // Do not remove this code, once we have the data in the correct format, we can use this function to filter the data + // const recursiveFn = (obj) => { + // if (!obj || typeof obj !== 'object') return []; + // let isCompletelyExposed = false; + // obj?.children?.forEach((child) => { + // const { id } = child; + // if (searchedSet.has(id)) { + // isCompletelyExposed = true; + // } + // }); + // const newChildren = + // obj?.children + // ?.filter((child) => { + // return isCompletelyExposed || pathSet.has(child.id); + // }) + // ?.map((child) => { + // return recursiveFn(child); + // }) || []; + + // return { + // ...obj, + // children: newChildren, + // }; + // }; + + // const formattedData = useMemo(() => { + // return searchValue ? recursiveFn(data) : data; + // }, [data, searchValue]); + + const key = useMemo(() => { + return uuidv4(); + }, [JSON.stringify(data), selectedNodePath]); + + const processedData = useMemo(() => ensureUniqueIds(data), [data]); + const flattendedData = flattenTree(processedData); + + const backFn = () => { + setSelectedNodePath(null); + }; + + const selectedData = (() => { + if (selectedNodePath?.startsWith('components.')) { + // Split the selectedNode path using . and grab the second element if it exists + const pathArray = selectedNodePath.split('.'); + const componentName = pathArray?.[1]; + const componentId = getComponentIdFromName(componentName); + const component = getComponentDefinition(componentId); + const parent = component?.component?.parent; + if (parent) { + const parentComponent = getComponentDefinition(parent); + const parentType = parentComponent?.component?.component; + if (parentType === 'Form') { + return { + id: componentId, + }; + } + } + } + return selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {}; + })(); + + const expandedIds = [...Array.from(pathSet), ...selectedNodes]; + + const filteredIds = useMemo(() => { + const expandedIdsSet = new Set(expandedIds); + const filtered = flattendedData.filter((item) => { + const { metadata } = item || {}; + const { actualPath, path } = metadata || {}; + return expandedIdsSet.has(actualPath || path); + }); + + return filtered + .map((item) => item.id) + .filter((path) => { + const pathArray = path.split('.'); + // One by one combine and check if the path is in expandedIds or not + for (let i = pathArray.length - 1; i > 0; i--) { + const parentPath = pathArray.slice(0, i).join('.'); + if (!expandedIdsSet.has(parentPath)) { + return false; + } + } + return true; + }); + }, [flattendedData, expandedIds]); + + return ( + <> + {!selectedNodePath || (typeof selectedData == 'object' && isEmpty(selectedData)) ? ( +
+
+ {/* setSearchValue(e.target.value)} + onClearCallback={() => setSearchValue('')} + placeholder={`Search`} + customClass={`tj-inspector-search-input tj-text-xsm`} + showClearButton={false} + width={300} + /> */} + + setSearchValue(e.target.value)} + onClear={() => setSearchValue('')} + size="medium" + placeholder="Search" + value={searchValue} + {...(searchValue && { trailingAction: 'clear' })} + data-cy="inspector-search-input" + /> +
+
+ { + const { element } = props; + const { metadata } = element || {}; + const { path } = metadata || {}; + const data = { + nodeName: element.name, + selectedNodePath: path, + }; + + return ( + + ); + }} + /> +
+
+ ) : ( + + )} + + ); +}; + +export default JSONTreeViewerV2; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx new file mode 100644 index 0000000000..e1391adc2e --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { TreeViewHeader } from './TreeViewHeader'; +import useCallbackActions from './useCallbackActions'; +import CustomJSONViewer from './CustomJSONViewer/CustomJSONViewer'; + +export const JSONViewer = (props) => { + const { data, path, darkMode, backFn, iconsList } = props; + + const callbackActions = useCallbackActions() || []; + const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions'; + const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for))?.[0]?.actions; + const optionsData = { + nodeName: path?.split('.')?.slice(-1)?.[0] || '', + selectedNodePath: path, + }; + + const generalActions = callbackActions.filter((action) => action.for === 'all')?.[0]?.actions || []; + + return ( +
+ + +
+ ); +}; + +export default JSONViewer; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 3adca4be98..6b8e52ce4b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -1,108 +1,153 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { HeaderSection } from '@/_ui/LeftSidebar'; import JSONTreeViewer from '@/_ui/JSONTreeViewer'; +import JSONTreeViewerV2 from './JSONTreeViewerV2'; import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; -import useCallbackActions from './useCallbackActions'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button'; +import { formatInspectorDataMisc, formatInspectorQueryData } from './utils'; +import ErrorBoundary from '@/_ui/ErrorBoundary'; -const sortAndReduce = (obj) => { - return Object.entries(obj) - .sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })) - .reduce((acc, [name, value]) => { - acc[name] = value; - return acc; - }, {}); -}; +import './styles.scss'; -const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { +const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType }) => { const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); const exposedQueries = useStore((state) => state.getAllExposedValues().queries || {}, shallow); const exposedVariables = useStore((state) => state.getAllExposedValues().variables || {}, shallow); const exposedConstants = useStore((state) => state.getAllExposedValues().constants || {}, shallow); const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow); const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); + const exposedModuleInputs = useStore((state) => state.getAllExposedValues(moduleId).input || {}, shallow); const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); + const formatInspectorComponentData = useStore((state) => state.formatInspectorComponentData, shallow); const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); - const pathToBeInspected = useStore((state) => state.pathToBeInspected); + const searchablePaths = useRef(new Set(['queries', 'components', 'globals', 'variables', 'page', 'constants'])); + const iconsList = useIconList({ exposedComponentsVariables, componentIdNameMapping, exposedQueries, }); - const callbackActions = useCallbackActions(); const sortedComponents = useMemo(() => { - return Object.entries(componentIdNameMapping) - .map(([key, name]) => ({ - key, - name: name || key, - value: exposedComponentsVariables[key] ?? { id: key }, - })) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) - .reduce((acc, { key, name, value }) => { - acc[name] = { ...value, id: key }; - return acc; - }, {}); + return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables, searchablePaths.current); }, [exposedComponentsVariables, componentIdNameMapping]); const sortedQueries = useMemo(() => { - // Create a reverse mapping for faster lookups - const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); - - const _sortedQueries = Object.entries(exposedQueries) - .map(([key, value]) => ({ - key, - name: reverseMapping[key] || key, - value, - })) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) - .reduce((acc, { name, value }) => { - acc[name] = value; - return acc; - }, {}); - return _sortedQueries; + return formatInspectorQueryData(queryNameIdMapping, exposedQueries, searchablePaths.current); }, [exposedQueries, queryNameIdMapping]); - const sortedVariables = useMemo(() => sortAndReduce(exposedVariables), [exposedVariables]); + const sortedVariables = useMemo( + () => formatInspectorDataMisc(exposedVariables, 'variables', searchablePaths.current), + [exposedVariables] + ); - const sortedConstants = useMemo(() => sortAndReduce(exposedConstants), [exposedConstants]); + const sortedConstants = useMemo( + () => formatInspectorDataMisc(exposedConstants, 'constants', searchablePaths.current), + [exposedConstants] + ); - const sortedPageVariables = useMemo(() => sortAndReduce(exposedPageVariables), [exposedPageVariables]); + const sortedPageVariables = useMemo( + () => formatInspectorDataMisc(exposedPageVariables, 'page', searchablePaths.current), + [exposedPageVariables] + ); - const sortedGlobalVariables = useMemo(() => sortAndReduce(exposedGlobalVariables), [exposedGlobalVariables]); + const sortedGlobalVariables = useMemo( + () => formatInspectorDataMisc(exposedGlobalVariables, 'globals', searchablePaths.current), + [exposedGlobalVariables] + ); + + const sortedModuleInputs = useMemo( + () => formatInspectorDataMisc(exposedModuleInputs, 'input', searchablePaths.current), + [exposedModuleInputs] + ); const memoizedJSONData = React.useMemo(() => { - const jsontreeData = {}; + const jsontreeData = { + name: '', + children: [ + { + id: 'queries', + name: 'Queries', - jsontreeData['queries'] = sortedQueries; - jsontreeData['components'] = sortedComponents; - jsontreeData['globals'] = sortedGlobalVariables; - jsontreeData['variables'] = sortedVariables; - jsontreeData['page'] = sortedPageVariables; - jsontreeData['constants'] = sortedConstants; + children: sortedQueries, + metadata: { type: 'queries', path: 'queries' }, + }, + { + id: 'components', + name: 'Components', + + children: sortedComponents, + metadata: { type: 'components', path: 'components' }, + }, + { + id: 'globals', + name: 'Globals', + + children: sortedGlobalVariables, + metadata: { type: 'globals', path: 'globals' }, + }, + { + id: 'variables', + name: 'Variables', + + children: sortedVariables, + metadata: { type: 'variables', path: 'variables' }, + }, + { + id: 'page', + name: 'Page', + + children: sortedPageVariables, + metadata: { type: 'page', path: 'page' }, + }, + { + id: 'constants', + name: 'Constants', + children: sortedConstants, + metadata: { type: 'constants', path: 'constants' }, + }, + ], + }; + + if (appType === 'module') { + jsontreeData.children.push({ + id: 'input', + name: 'Input', + children: sortedModuleInputs, + metadata: { path: 'input' }, + }); + } + + const addNoDataChild = (data) => { + const types = data.children; + types.forEach((type) => { + if (type.children.length === 0) { + type.children.push({ + id: `empty-${type.metadata.type}`, + name: `No ${type.metadata.type} found`, + children: [], + metadata: { noData: true }, + }); + } + }); + }; + + addNoDataChild(jsontreeData); return jsontreeData; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); - - const handleNodeExpansion = (path, data, currentNode) => { - if (pathToBeInspected && path?.length > 0) { - const shouldExpand = pathToBeInspected.includes(path[path.length - 1]); - - // Scroll to the component in the inspector - if (path?.length === 2 && path?.[0] === 'components' && shouldExpand) { - const target = document.getElementById(`inspector-node-${String(currentNode).toLowerCase()}`); - if (target) { - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } - } - - return shouldExpand; - } else return false; - }; + }, [ + sortedComponents, + sortedQueries, + sortedVariables, + sortedConstants, + sortedPageVariables, + sortedGlobalVariables, + sortedModuleInputs, + ]); return (
{ style={{ resize: 'horizontal', minWidth: 288 }} > - +
- setPinned(!pinned)} - darkMode={darkMode} - styles={{ width: '28px', padding: 0 }} - data-cy={`left-sidebar-inspector`} - variant="tertiary" - className="left-sidebar-header-btn" - leftIcon={pinned ? 'unpin' : 'pin'} - iconWidth="14" - fill={`var(--slate12)`} - > + variant="ghost" + fill="var(--icon-strong,#6A727C)" + size="medium" + data-cy="left-sidebar-pin-button" + />
+
- + + +
); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx new file mode 100644 index 0000000000..081737f3f5 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -0,0 +1,163 @@ +import React, { useState } from 'react'; +import WidgetIcon from '@/../assets/images/icons/widgets'; +import { extractComponentName } from './utils'; +import { ToolTip } from '@/_components/ToolTip'; +import Highlighter from 'react-highlight-words'; +import cx from 'classnames'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import OverflowTooltip from '@/_components/OverflowTooltip'; +import { HiddenOptions } from './HiddenOptions'; +import useCallbackActions from './useCallbackActions'; +import useStore from '@/AppBuilder/_stores/store'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button'; +import { shallow } from 'zustand/shallow'; + +const renderNodeIcons = (node, iconsList, darkMode) => { + const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; + if (icon && icon?.iconPath) { + return ( + + ); + } + + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx({ height: 14, width: 14 })}
+
+ ); + } + return icon.jsx({ height: 14, width: 14 }); + } +}; + +export const Node = (props) => { + const { + element, + getNodeProps, + level, + handleSelect, + handleExpand, + isExpanded, + isDisabled, + isBranch, + darkMode, + setSelectedNodePath, + searchValue, + iconsList, + data, + } = props; + + const [actionClicked, setActionClicked] = useState(false); + const setSelectedNodes = useStore((state) => state.setSelectedNodes, shallow); + const callbackActions = useCallbackActions() || []; + const nodeIcon = renderNodeIcons(element.name, iconsList, darkMode); + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const metadata = element.metadata || {}; + const { type, path } = metadata; + const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for)); + const onExpand = (node) => { + const { element } = node || {}; + const { metadata } = element || {}; + const { path, actualPath } = metadata || {}; + setSelectedNodes(actualPath || path); + }; + + const onSelect = (node) => { + const { isBranch, element } = node || {}; + const { metadata } = element || {}; + const { path, type } = metadata || {}; + if (type && level !== 1) { + setSelectedNodePath(path); + } + }; + + const nodeSpecificFilteredActions = + nodeSpecificActions?.[0]?.actions?.filter((action) => { + return action.enableInspectorTreeView; + }) || []; + + const generalActions = callbackActions.filter((action) => action.for === 'all'); + const generalActionsFiltered = generalActions?.[0]?.actions?.filter((action) => { + return action.enableInspectorTreeView; + }); + + return ( + //
+
1 ? 12 : 0, + // paddingLeft: '16px', + opacity: isDisabled ? 0.5 : 1, + height: '24px', + display: 'flex', + alignItems: 'center', + color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', + fontWeight: level === 1 ? 500 : 400, + marginTop: level === 1 ? 4 : 0, + marginBottom: level === 1 ? 4 : 0, + // borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none', + }} + > + {/* {!['queries', 'globals', 'variables'].includes(type) && ( */} +
+ {(isBranch || level === 1 || path === 'page.variables') && ( + onExpand(props)} + variant="ghost" + fill="var(--icon-default,#ACB2B9)" + size="small" + data-cy={`inspector-${type}-expand-button`} + /> + )} +
+ {/* )} */} + +
onSelect(props)} + className={cx('node-content', { + 'node-content-hoverable': level !== 1 && !metadata.noData, + 'node-content-active': actionClicked, + })} + > + {nodeIcon &&
{nodeIcon}
} +
+ {metadata.noData && ( +
+ + {element.name} +
+ )} + {!metadata.noData && ( + + + + )} +
+
+ +
+
+
+ //
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx new file mode 100644 index 0000000000..3ef719de00 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from 'react'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import ArrowLeft from '@/_ui/Icon/bulkIcons/Arrowleft'; +import CheveronRight from '@/_ui/Icon/bulkIcons/CheveronRight'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import cx from 'classnames'; +import { ToolTip } from '@/_components/ToolTip'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { DefaultCopyIcon } from './DefaultCopyIcon'; +import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers'; + +export const TreeViewHeader = (props) => { + const { path, backFn, darkMode, data, nodeSpecificActions, type, generalActions } = props; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const [showMenu, setShowMenu] = useState(false); + const pathArray = path.split('.'); + const parentNode = pathArray[0]; + + const closeMenu = () => { + setShowMenu(false); + }; + + const copyPath = () => { + generalActions[0].dispatchAction(`{{${data?.selectedNodePath}}}`, false); + }; + + const copyValue = () => { + const value = getResolvedValue(`{{${data?.selectedNodePath}}}`); + generalActions[0].dispatchAction(value); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (event.target.closest('.copy-menu-options') === null) { + closeMenu(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderOptions = () => { + return ( + <> +
+
{ + event.stopPropagation(); + copyPath(); + closeMenu(); + }} + className="option" + data-cy="inspector-copy-path" + > + + Copy path +
+
{ + event.stopPropagation(); + copyValue(); + closeMenu(); + }} + className="option" + data-cy="inspector-copy-value" + > + + Copy value +
+
+ + {nodeSpecificActions?.map((actionOption, index) => { + const { name, icon, src, iconName, dispatchAction, width = 16, height = 16 } = actionOption; + if (icon) { + return ( +
+ { + event.stopPropagation(); + dispatchAction(data); + setShowMenu(false); + }} + className="option" + > + + {name} + +
+ ); + } + })} + + ); + }; + + return ( +
+ {/*
+ +
*/} +
+ {parentNode.charAt(0).toUpperCase() + parentNode.slice(1)} + + {pathArray.length > 1 && + pathArray.slice(1).map((item, index) => ( + <> + + + {item.charAt(0).toUpperCase() + item.slice(1)} + + + ))} +
+ + + {renderOptions()} + + } + > +
{ + event.stopPropagation(); + setShowMenu((prev) => !prev); + }} + className="copy-menu-options-icon json-viewer-options-btn" + style={{ + outline: 'none', + border: 'none', + boxShadow: 'none', + }} + data-cy="inspector-menu-icon" + > + +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss new file mode 100644 index 0000000000..6a2c981600 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/styles.scss @@ -0,0 +1,288 @@ +.json-tree-view { + // ul { + // margin-left:16px !important; + // border-left: 1px solid var(--slate6, #D7DBDF); + // } + + ul[role="tree"] { + margin-left: 16px !important; + border-left: none !important; + + ul { + margin-left: 16px !important; + border-left: none !important; + + ul { + margin-left: 10px !important; + padding-left: 16px !important; + border-left: 1px solid var(--slate6, #D7DBDF) !important; + } + } + + } +} + +.basic.tree { + list-style: none; + margin: 0; + padding: 0px; + padding-right: 20px; + padding-top: 0px; + +} + +.basic .tree-node, +.basic .tree-node-group { + list-style: none; + margin: 0; + padding: 0; +} + +.basic .tree-branch-wrapper, +.basic .tree-node__leaf { + outline: none; +} + +// .basic .tree-node--focused { +// outline-color: rgb(77, 144, 254); +// outline-style: auto; +// outline-width: 2px; +// display: block; +// } + +.basic .tree-node__branch { + display: block; +} + +.basic .tree-node { + cursor: pointer; +} + +.node-expansion-icon { + width: 20px; + height: 20px; + margin-right: 4px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.node-icon { + display: flex; + align-items: flex-end; + justify-content: center; + margin-right: 6px; + height: 14px; + width: 14px; +} + +.node-label { + display: flex; + align-items: center; + font-size: 12px; + flex: 1; + min-width: 0px; +} + + +.tj-inspector-search-input { + width: 300px; + height: 32px; + border-radius: 6px; + background-color: var(--base) !important; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; +} + +.node-content { + display: flex; + align-items: center; + height: 100%; + width: 100%; + padding-left: 4px; + padding-right: 4px; + +} + +.node-content-active { + .copy-menu-options-icon { + background-color: var(--interactive-overlays-fill-hover); + } +} + +.node-content-hoverable:hover, +.node-content-active { + background-color: var(--interactive-overlays-fill-hover); + cursor: pointer; + + .node-actions { + display: flex; + } + +} + +.json-viewer { + font-size: 11px; + + ul { + background-color: transparent !important; + padding-left: 6px !important; + padding-right: 10px !important; + // li{ + // text-indent: 0px !important; + // padding-top: 0px !important; + // height:18.46px; + // } + // ul > li:hover { + // background-color: var(--interactive-overlays-fill-hover); // or any color you prefer + // } + } + + + +} + +.json-viewer-node-value { + font-weight: 500; +} + +.json-viewer-header { + font-size: 12px; + font-weight: 500; + display: flex; + flex-direction: row; + margin-left: 16px; + margin-bottom: 12px; + margin-right: 18px; + margin-top: 8px; + height: 28px; + align-items: center; + +} + +.json-viewer-back-btn { + display: flex; + align-items: center; + + &:hover { + cursor: pointer; + background-color: var(--interactive-overlays-fill-hover); + border-radius: 4px; + } + +} + +// .json-viewer-options-btn { +// display: flex; +// align-items: center; +// margin-left: auto; + +// &:hover { +// cursor: pointer; +// background-color:var(--interactive-overlays-fill-hover); +// border-radius: 4px; +// } +// } + +.json-viewer-options-btn { + margin-left: auto; + + + align-items: center; + +} + + +.node-highlight { + background-color: #FFD43B; + padding: 0px; +} + +.node-actions { + justify-content: center; + align-items: center; + margin-left: auto; + display: none; + gap: 4px; +} + +.node-action-icon { + display: flex; + justify-content: center; + align-items: center; + height: 20px; + width: 20px; + border-radius: 4px; + flex-shrink: 0; + + &:hover { + background-color: var(--button-outline-hover); + } + +} + +.copy-menu-options-icon { + border: 1px solid var(--border-weak, #E4E7EB); + display: flex; + justify-content: center; + align-items: center; + width: 20px; + height: 20px; + border-radius: 4px; + box-shadow: var(--elevation-100-box-shadow); + background-color: var(--base); + + &:hover { + background-color: var(--button-outline-hover); + } +} + +.copy-menu-options { + width: 144px; + border: none; + background-color: var(--background-surface-layer-01); + border-radius: 10px; + top: -5px !important; + + &.dark-theme { + .popover-body { + box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.9), 0px 8px 16px 0px #000000; + } + } + + .popover-body { + width: 100%; + padding: 8px; + border-radius: 10px; + background: var(--background-surface-layer-01); + box-shadow: 0px 0px 1px 0px rgba(48, 50, 51, 0.05), 0px 8px 16px 0px rgba(48, 50, 51, 0.1); + + .menu-options { + .option { + display: flex; + padding: 6px 8px; + align-items: center; + gap: 6px; + height: 30px; + align-self: stretch; + border-radius: 6px; + cursor: pointer; + + &:hover { + background: rgba(136, 144, 153, 0.08); + } + } + } + } +} + +.node-label-no-data { + display: flex; + align-items: center; + gap: 4px; + color: var(--text-placeholder, #858C94); + +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 937fe45b2c..1c4685b585 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -1,7 +1,7 @@ import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -// import { runQuery } from '@/AppBuilder/_utils/queryPanel'; +import { copyToClipboard } from './utils'; const useCallbackActions = () => { const deleteComponents = useStore((state) => state.deleteComponents, shallow); @@ -10,29 +10,39 @@ const useCallbackActions = () => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); const runQuery = useStore((state) => state.queryPanel.runQuery); const getComponentIdToAutoScroll = useStore((state) => state.getComponentIdToAutoScroll); + const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery, shallow); + const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow); + const getQueryIdFromName = useStore((state) => state.getQueryIdFromName, shallow); const handleRemoveComponent = (component) => { - deleteComponents([component.id]); + const { nodeName } = component; + const componentId = getComponentIdFromName(nodeName); + deleteComponents([componentId]); }; const handleSelectComponentOnEditor = (component) => { - if (currentPageComponents?.[component.id]) { - setSelectedComponents([component.id]); + const { nodeName } = component; + const componentId = getComponentIdFromName(nodeName); + if (currentPageComponents?.[componentId]) { + setSelectedComponents([componentId]); } }; - const handleRunQuery = (query, currentNode) => { - runQuery(query.id, currentNode, undefined, 'edit', {}, true); + const handleRunQuery = (data) => { + const { nodeName } = data; + const queryId = getQueryIdFromName(nodeName); + runQuery(queryId, nodeName, undefined, 'edit', {}, true); }; - const copyToClipboard = (data) => { - const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); - navigator.clipboard.writeText(stringified); - return toast.success('Copied to the clipboard', { position: 'top-center' }); + const selectQuery = (data) => { + const { nodeName } = data; + const id = getQueryIdFromName(nodeName); + setSelectedQuery(id); }; const handleAutoScrollToComponent = (data) => { - const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(data.id); + const componentId = getComponentIdFromName(data.nodeName); + const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(componentId); if (!isAccessible) { if (isOnCanvas) { toast.success( @@ -57,9 +67,19 @@ const useCallbackActions = () => { name: 'Run Query', dispatchAction: handleRunQuery, icon: true, - src: 'assets/images/icons/editor/play.svg', + iconName: 'play01', width: 8, height: 8, + enableInspectorTreeView: false, + }, + { + name: 'View query', + dispatchAction: selectQuery, + icon: true, + iconName: 'file-code', + width: 14, + height: 14, + enableInspectorTreeView: true, }, ], enableForAllChildren: false, @@ -68,10 +88,30 @@ const useCallbackActions = () => { { for: 'components', actions: [ - { name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true }, - { name: 'Go to component', dispatchAction: handleAutoScrollToComponent, icon: true, iconName: 'select' }, + { + name: 'Select Widget', + dispatchAction: handleSelectComponentOnEditor, + icon: false, + onSelect: true, + enableInspectorTreeView: false, + }, + { + name: 'Go to component', + dispatchAction: handleAutoScrollToComponent, + icon: true, + iconName: 'corners', + enableInspectorTreeView: true, + }, ...(!shouldFreeze - ? [{ name: 'Delete Component', dispatchAction: handleRemoveComponent, icon: true, iconName: 'trash' }] + ? [ + { + name: 'Delete Component', + dispatchAction: handleRemoveComponent, + icon: true, + iconName: 'trash', + enableInspectorTreeView: false, + }, + ] : []), ], enableForAllChildren: false, @@ -79,7 +119,10 @@ const useCallbackActions = () => { }, { for: 'all', - actions: [{ name: 'Copy value', dispatchAction: copyToClipboard, icon: false }], + actions: [ + { name: 'Copy value', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true }, + { name: 'Copy path', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true }, + ], }, ]; return callbackActions; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js index e93c18d959..ab818d9786 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useIconList.js @@ -10,7 +10,10 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos const queryIcons = Object.keys(exposedQueries).map((queryId) => { const query = dataQueries.find((dataQuery) => dataQuery.id === queryId); if (!isEmpty(query)) { - return { iconName: query?.name, jsx: () => }; + return { + iconName: query?.name, + jsx: ({ height = 16, width = 16 }) => , + }; } }); @@ -37,7 +40,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.disable) { icons.push({ iconName: 'disable', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative', isInfoIcon: true, @@ -47,7 +52,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.visibility) { icons.push({ iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, @@ -62,7 +69,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.setChecked) { icons.push({ iconName: 'setChecked', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setValue as an alternative', isInfoIcon: true, @@ -78,7 +87,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.disable) { icons.push({ iconName: 'disable', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative', isInfoIcon: true, @@ -88,7 +99,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.visibility) { icons.push({ iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, @@ -98,7 +111,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.loading) { icons.push({ iconName: 'loading', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setLoading as an alternative', isInfoIcon: true, @@ -112,7 +127,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos return [ { iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, @@ -124,7 +141,6 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos }) .flat() .filter((value) => value !== undefined); // Remove undefined values - const iconsList = useMemo( () => [...queryIcons, ...componentIcons, ...deprecatedIcons], [queryIcons, componentIcons, deprecatedIcons] diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js new file mode 100644 index 0000000000..87ee5306a2 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -0,0 +1,92 @@ +import { toast } from 'react-hot-toast'; + +export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) => { + if (typeof obj !== 'object' || obj === null) return []; + const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })); + const reduceData = (obj, path, level = 1) => { + let data = obj; + if (!obj || typeof obj !== 'object' || (path === 'page.variables' ? level > 2 : level > 1)) return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data.reduce((acc, [name, value]) => { + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: type, + path: currentPath, + ...((path === 'page.variables' ? level === 2 : level === 1) && { + data: typeof value === 'object' ? JSON.stringify(value) : value, + }), + }, + }, + ]; + }, []); + }; + + return reduceData(data, type); +}; + +export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries, searchablePaths = new Set()) => { + const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); + const _sortedQueries = Object.entries(exposedQueries) + .map(([key, value]) => ({ + key, + name: reverseMapping[key] || key, + value, + })) + .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + + const reduceData = (obj, path = 'queries', level = 1) => { + let data = obj; + if (!obj || typeof obj !== 'object' || level > 1) return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data + .filter((item) => item.name) + .reduce((acc, { id, name, value }) => { + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: 'queries', + path: currentPath, + ...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }), + }, + }, + ]; + }, []); + }; + + return reduceData(_sortedQueries); +}; + +export const extractComponentName = (path) => { + // Match the last part of the URL before ".svg" using a regular expression + const match = path.match(/\/([^/]+)\.svg$/); + + if (match && match[1]) { + return match[1]; // Return the matched component name + } else { + return null; // Return null if the pattern doesn't match + } +}; + +export const copyToClipboard = (data, includeQuotes = true) => { + const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); + const finalText = includeQuotes ? stringified : stringified.slice(1, -1); + navigator.clipboard.writeText(finalText); + return toast.success('Copied to the clipboard', { position: 'top-center' }); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx index 497b697b25..d57e82c0c6 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx @@ -2,11 +2,13 @@ import React from 'react'; import { Overlay, Popover } from 'react-bootstrap'; import { Button } from '@/_ui/LeftSidebar'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import SolidIcon from '@/_ui/Icon/SolidIcons'; export const PageHandlerMenu = ({ darkMode }) => { + const { moduleId } = useModuleContext(); const setShowEditingPopover = useStore((state) => state.setShowEditingPopover); const setShowPageEventsModal = useStore((state) => state.setShowPageEventsModal); @@ -31,7 +33,7 @@ export const PageHandlerMenu = ({ darkMode }) => { closePageEditPopover(); }; - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const page = editingPage; const isHomePage = page?.id === homePageId; const showMenu = showEditingPopover; @@ -105,7 +107,7 @@ export const PageHandlerMenu = ({ darkMode }) => { text="Mark home" iconSrc={'assets/images/icons/home.svg'} closeMenu={() => {}} - callback={() => markAsHomePage(editingPage.id)} + callback={() => markAsHomePage(editingPage.id, moduleId)} /> )} {!isDisabled && ( diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx index 6c74e7b6af..3f8b3c09d3 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx @@ -16,14 +16,16 @@ import { RenameInput } from './RenameInput'; import IconSelector from './IconSelector'; import { withRouter } from '@/_hoc/withRouter'; import OverflowTooltip from '@/_components/OverflowTooltip'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; export const PageMenuItem = withRouter( memo(({ darkMode, page, navigate }) => { - const homePageId = useStore((state) => state.app.homePageId); + const { moduleId } = useModuleContext(); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const isHomePage = page.id === homePageId; - const currentPageId = useStore((state) => state.currentPageId); + const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); const isSelected = page.id === currentPageId; const isHidden = page?.hidden ?? false; const isDisabled = page?.disabled ?? false; @@ -140,9 +142,9 @@ export const PageMenuItem = withRouter( if (currentPageId === page.id) { return; } - switchPage(page.id, page.handle); - setCurrentPageHandle(page.handle); - }, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle]); + switchPage(page.id, page.handle, [], moduleId); + setCurrentPageHandle(page.handle, moduleId); + }, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle, moduleId]); const handlePageMenuSettings = useCallback( (event) => { diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx index 88a0e2664a..e903078dc2 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx @@ -10,12 +10,12 @@ import { Button } from 'react-bootstrap'; import { decodeEntities } from '@/_helpers/utils'; import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { Button as ButtonComponent } from '@/components/ui/Button/Button'; import { debounce } from 'lodash'; export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const updateQuerySuggestions = useStore((state) => state.queryPanel.updateQuerySuggestions); const previewQuery = useStore((state) => state.queryPanel.previewQuery); const renameQuery = useStore((state) => state.dataQuery.renameQuery); @@ -152,8 +152,8 @@ const NameInput = ({ onInput, value, darkMode, isDiabled, selectedQuery }) => { const hasPermissions = selectedDataSourceScope === 'global' ? canUpdateDataSource(selectedQuery?.data_source_id) || - canReadDataSource(selectedQuery?.data_source_id) || - canDeleteDataSource() + canReadDataSource(selectedQuery?.data_source_id) || + canDeleteDataSource() : true; const inputRef = useRef(); @@ -275,8 +275,8 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => { const hasPermissions = selectedDataSource?.scope === 'global' && selectedDataSource?.type !== DATA_SOURCE_TYPE.SAMPLE ? canUpdateDataSource(selectedQuery?.data_source_id) || - canReadDataSource(selectedQuery?.data_source_id) || - canDeleteDataSource() + canReadDataSource(selectedQuery?.data_source_id) || + canDeleteDataSource() : true; const isPreviewQueryLoading = useStore((state) => state.queryPanel.isPreviewQueryLoading); const { t } = useTranslation(); diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx index dcbf7b46ce..60210d4d07 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx @@ -5,15 +5,17 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; import './workflows-query.scss'; import { v4 as uuidv4 } from 'uuid'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver'; export function Workflows({ options, optionsChanged, currentState }) { + const { moduleId } = useModuleContext(); 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); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); usePopoverObserver( document.getElementsByClassName('query-details')[0], diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx index e0ddb28922..2f4b785b6a 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx @@ -5,7 +5,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; -export const ComponentConfigurationTab = ({ darkMode }) => { +export const ComponentConfigurationTab = ({ darkMode, isModuleEditor }) => { const selectedComponentId = useStore((state) => state.selectedComponents?.[0], shallow); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); if (!selectedComponentId) { @@ -17,6 +17,7 @@ export const ComponentConfigurationTab = ({ darkMode }) => { darkMode={darkMode} selectedComponentId={selectedComponentId} pages={[]} + isModuleEditor={isModuleEditor} /> ); }; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx new file mode 100644 index 0000000000..f1126ab4d5 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import './styles.scss'; + +export const ComponentModuleTab = ({ onChangeTab }) => { + const [activeTab, setActiveTab] = useState(1); + + const handleChangeTab = (tab) => { + setActiveTab(tab); + onChangeTab(tab); + }; + + return ( +
+
+ + +
+
+ ); +}; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js new file mode 100644 index 0000000000..a9bf63add0 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js @@ -0,0 +1 @@ +export { ComponentModuleTab as default } from './ComponentModuleTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss new file mode 100644 index 0000000000..51fdce0d61 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss @@ -0,0 +1,19 @@ +.tj-tabs-container-outer { + padding-top: 0px; + gap: 10px; + height: 36px; + margin-bottom: 8px; + margin-top: 16px; +} + +.tj-tabs-container { + padding: 2px; + gap: 2px; + display: flex; + + width: 100%; + height: 100%; + background: var(--slate4); + border-radius: 6px; + +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx similarity index 78% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx index ae5aadfac6..7968e72e89 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx @@ -1,24 +1,30 @@ import React, { useCallback, useMemo, useState } from 'react'; import { isEmpty, debounce } from 'lodash'; import { useTranslation } from 'react-i18next'; -import { LEGACY_ITEMS } from './constants'; +import { LEGACY_ITEMS, IGNORED_ITEMS } from './constants'; import { componentTypes, componentTypeDefinitionMap } from '@/AppBuilder/WidgetManager'; import Fuse from 'fuse.js'; import { SearchBox } from '@/_components'; import { DragLayer } from './DragLayer'; import useStore from '@/AppBuilder/_stores/store'; +import ComponentModuleTab from './ComponentModuleTab'; +import { ModuleManager } from '@/modules/Modules/components'; // TODO: Hardcode all the component-section mapping in a constant file and just loop over it // TODO: styling // TODO: scrolling // TODO: searching -export const ComponentsManagerTab = ({ darkMode }) => { +export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { const componentList = useMemo(() => { - return componentTypes.map((component) => component.component); + return componentTypes + .map((component) => component.component) + .filter((component) => !IGNORED_ITEMS.includes(component)); }, [componentTypes]); const [filteredComponents, setFilteredComponents] = useState(componentList); + const [searchQuery, setSearchQuery] = useState(''); + const [activeTab, setActiveTab] = useState(1); const _shouldFreeze = useStore((state) => state.getShouldFreeze()); const isAutoMobileLayout = useStore((state) => state.currentLayout === 'mobile' && state.getIsAutoMobileLayout()); const shouldFreeze = _shouldFreeze || isAutoMobileLayout; @@ -26,9 +32,14 @@ export const ComponentsManagerTab = ({ darkMode }) => { const handleSearchQueryChange = useCallback( debounce((e) => { const { value } = e.target; - filterComponents(value); + setSearchQuery(value); + + if (activeTab === 1) { + filterComponents(value); + } + // No need to filter modules here as we pass searchQuery to ModuleManager }, 125), - [] + [activeTab] ); const filterComponents = useCallback((value) => { @@ -160,24 +171,48 @@ export const ComponentsManagerTab = ({ darkMode }) => { } } + const handleChangeTab = (tab) => { + setActiveTab(tab); + // When changing tabs, we don't need to reset the search + // The search query will be applied to the new tab + }; + + const renderSection = () => { + if (activeTab === 1) { + return
{segregateSections()}
; + } + return ; + }; + return (
-

Components

+ {isModuleEditor ? ( +

Components

+ ) : ( + + )}
handleSearchQueryChange(e)} onClearCallback={() => { - filterComponents(''); + setSearchQuery(''); + if (activeTab === 1) { + filterComponents(''); + } }} - placeholder={t('globals.searchComponents', 'Search widgets')} - customClass={`tj-widgets-search-input tj-text-xsm`} + placeholder={ + activeTab === 1 + ? t('globals.searchComponents', 'Search widgets') + : t('globals.searchModules', 'Search modules') + } + customClass={`tj-widgets-search-input tj-text-xsm`} showClearButton={false} width={266} />
-
{segregateSections()}
+ {renderSection()}
); }; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx similarity index 70% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index 9589ac145b..0c6f7327af 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -1,11 +1,18 @@ import React, { useEffect } from 'react'; import { WidgetBox } from '../WidgetBox'; +import { ModuleWidgetBox } from '@/modules/Modules/components'; import { useDrag, useDragLayer } from 'react-dnd'; import { getEmptyImage } from 'react-dnd-html5-backend'; import { snapToGrid } from '@/AppBuilder/AppCanvas/appCanvasUtils'; import { NO_OF_GRIDS } from '@/AppBuilder/AppCanvas/appCanvasConstants'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; +import { noop } from 'lodash'; -export const DragLayer = ({ index, component }) => { +export const DragLayer = ({ index, component, isModuleTab = false }) => { + const { isModuleEditor } = useModuleContext(); + const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; const [{ isDragging }, drag, preview] = useDrag( () => ({ type: 'box', @@ -19,12 +26,25 @@ export const DragLayer = ({ index, component }) => { preview(getEmptyImage(), { captureDraggingState: true }); }, []); + useEffect(() => { + if (isDragging && !isModuleEditor) { + setShowModuleBorder(true); + } else { + setShowModuleBorder(false); + } + }, [isDragging, setShowModuleBorder, isModuleEditor]); + + // const size = isModuleTab + // ? component.module_container.layouts[currentLayout] + // : component.defaultSize || { width: 30, height: 40 }; + const size = component.defaultSize || { width: 30, height: 40 }; + return ( <> {isDragging && } -
- +
+ {isModuleTab ? : }
); diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/constants.js similarity index 67% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/constants.js index 19cfdb7032..778d2b5c66 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/constants.js @@ -7,3 +7,5 @@ export const LEGACY_ITEMS = [ 'Modal', 'TextArea', ]; + +export const IGNORED_ITEMS = ['ModuleContainer', 'ModuleViewer']; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js new file mode 100644 index 0000000000..6f24ff7948 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js @@ -0,0 +1 @@ +export { ComponentsManagerTab as default } from './ComponentsManagerTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js deleted file mode 100644 index ddb70f8220..0000000000 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './ComponentsManagerTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx index 8a24e5eb92..665f7b6755 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx @@ -9,6 +9,7 @@ import cx from 'classnames'; import useStore from '@/AppBuilder/_stores/store'; import styles from '@/_ui/Select/styles'; import moment from 'moment-timezone'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const DATE_FORMAT_OPTIONS = [ { @@ -101,12 +102,13 @@ const DatetimePickerV2 = ({ componentMeta, componentName, darkMode, ...restProps allComponents, pages, } = restProps; + const { moduleId } = useModuleContext(); const items = []; const additionalActions = []; const properties = []; const formatting = []; const validations = Object.keys(componentMeta.validation || {}); - const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties); + const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id, null, moduleId)?.properties); const isDateFormatFxOn = componentMeta?.definition?.properties?.dateFormat?.fxActive || false; const isTimeFormatFxOn = componentMeta?.definition?.properties?.timeFormat?.fxActive || false; const dateFormat = resolvedProperties?.dateFormat ?? resolvedProperties?.format; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx index 346d0bfc17..57da7ec318 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx @@ -6,7 +6,7 @@ import { renderElement } from '../Utils'; import i18next from 'i18next'; import { resolveReferences } from '@/_helpers/utils'; // import { AllComponents } from '@/Editor/Box'; -import { AllComponents } from '@/_helpers/editorHelpers'; +import { AllComponents } from '@/AppBuilder/_helpers/editorHelpers'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx index c74e023c0b..f2b0ff7594 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx @@ -20,14 +20,23 @@ export const Code = ({ placeholder, validationFn, isHidden = false, + customMeta, }) => { const currentState = useCurrentState(); - let initialValue = !_.isEmpty(definition) - ? definition.value - : getDefinitionInitialValue(paramType, param.name, component, currentState, definition.value); + function getInitialValue() { + if (customMeta && customMeta.defaultValue) { + return customMeta.defaultValue; + } + return !_.isEmpty(definition) + ? definition.value + : getDefinitionInitialValue(paramType, param.name, component, currentState, definition.value); + } - const paramMeta = accordian ? componentMeta[paramType]?.[param.name] : componentMeta[paramType][param.name]; + let initialValue = getInitialValue(); + const paramMeta = accordian + ? customMeta ?? componentMeta[paramType]?.[param.name] + : customMeta ?? componentMeta[paramType][param.name]; const displayName = paramMeta.displayName || param.name; function handleCodeChanged(value) { diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index e1c6bcf5c8..a5f5bb2202 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -30,6 +30,7 @@ import { appService } from '@/_services'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup'; import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem'; import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver'; @@ -48,10 +49,13 @@ export const EventManager = ({ customEventRefs = undefined, component, }) => { + const { moduleId, isModuleEditor } = useModuleContext(); const components = useStore((state) => state.getCurrentPageComponents()); const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup ); + const moduleInputDummyQueries = useStore((state) => state?.getModuleInputDummyQueries?.(), shallow) || {}; + const dataQueries = useStore((state) => { const queries = state.dataQuery?.queries?.modules?.canvas || []; if (callerQueryId) { @@ -62,7 +66,7 @@ export const EventManager = ({ const allAppEvents = useEvents(); const { createAppVersionEventHandlers, deleteAppVersionEventHandler, updateAppVersionEventHandlers } = useEventActions(); - const appId = useStore((state) => state.app.appId); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const eventsUpdatedLoader = useStore((state) => state.eventsSlice.getEventsUpdatedLoader(), shallow); const eventsCreatedLoader = useStore((state) => state.eventsSlice.getEventsCreatedLoader(), shallow); @@ -102,9 +106,9 @@ export const EventManager = ({ return a.index - b.index; }); - setEvents(sortedEvents || []); + setEvents(sortedEvents || [], moduleId); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(currentEvents)]); + }, [JSON.stringify(currentEvents), moduleId]); let groupedOptions = ActionTypes.reduce((acc, action) => { const groupName = action.group; @@ -454,6 +458,11 @@ export const EventManager = ({ return defaultValue; }; + const constructDataQueryOptions = () => { + const queries = dataQueries.filter((qry) => isQueryRunnable(qry)).map((qry) => ({ name: qry.name, value: qry.id })); + const moduleInputs = Object.entries(moduleInputDummyQueries).map(([key, value]) => ({ name: value, value: key })); + return [...moduleInputs, ...queries]; + }; const formatGroupLabel = (data) => { if (data.label === 'run-action') return; return ( @@ -687,27 +696,34 @@ export const EventManager = ({
setNewComponentName(e.target.value)} + type="text" + onBlur={() => handleComponentNameChange(newComponentName)} + className="w-100 inspector-edit-widget-name" + value={newComponentName} + ref={inputRef} + data-cy="edit-widget-name" + /> +
+ ); + }; + + const renderTabs = () => ( + + ); + return ( -
+
clearSelectedComponents()}> @@ -468,68 +502,50 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
-
-
- setNewComponentName(e.target.value)} - type="text" - onBlur={() => handleComponentNameChange(newComponentName)} - className="w-100 inspector-edit-widget-name" - value={newComponentName} - ref={inputRef} - data-cy="edit-widget-name" - /> -
-
-
- - - {INSPECTOR_HEADER_OPTIONS.map((option) => ( -
{ - e.stopPropagation(); - handleInspectorHeaderActions(option.value); - }} - > -
{option.icon}
+
{renderAppNameInput()}
+ {!isModuleContainer && ( +
+ + + {INSPECTOR_HEADER_OPTIONS.map((option) => (
{ + e.stopPropagation(); + handleInspectorHeaderActions(option.value); + }} > - {option?.label} +
{option.icon}
+
+ {option?.label} +
-
- ))} - - - } - > - setShowHeaderActionsMenu(true)}> - - - -
-
-
- - - {propertiesTab} - - - {stylesTab} - - + ))} + + + } + > + setShowHeaderActionsMenu(true)}> + + + +
+ )}
+ +
{renderTabs()}
@@ -749,6 +765,12 @@ const GetAccordion = React.memo( case 'CurrencyInput': return ; + case 'ModuleContainer': + return ; + + case 'ModuleViewer': + return ; + default: { return ; } diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js index 7e47f7f6c0..a9b981eb1a 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js @@ -28,13 +28,14 @@ export function renderCustomStyles( components = {}, accordian, darkMode = false, - placeholder = '' + placeholder = '', + customMeta ) { const componentConfig = component.component; const componentDefinition = componentConfig.definition; const paramTypeDefinition = componentDefinition[paramType] || {}; const definition = paramTypeDefinition[param] || {}; - const meta = componentMeta[paramType]?.[accordian]?.[param]; + const meta = customMeta ?? componentMeta[paramType]?.[accordian]?.[param]; if ( componentConfig.component == 'DropDown' || @@ -95,7 +96,7 @@ export function renderCustomStyles( return ( <> ); @@ -128,7 +130,8 @@ export function renderElement( components = {}, darkMode = false, placeholder = '', - validationFn + validationFn, + customMeta ) { const componentConfig = component.component; const componentDefinition = componentConfig.definition; @@ -158,7 +161,7 @@ export function renderElement( return ( ); } diff --git a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx index 88ea0f477a..3322b9e143 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx @@ -1,11 +1,13 @@ import React from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { ComponentConfigurationTab } from './ComponentConfigurationTab'; -import { ComponentsManagerTab } from './ComponentsManagerTab'; +import ComponentsManagerTab from './ComponentManagerTab'; import cx from 'classnames'; import { PageSettings } from './PageSettingsTab'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const RightSideBar = ({ darkMode }) => { + const { isModuleEditor } = useModuleContext(); const activeTab = useStore((state) => state.activeRightSideBarTab); const pageSettingSelected = useStore((state) => state.pageSettingSelected); @@ -15,9 +17,9 @@ export const RightSideBar = ({ darkMode }) => {
{pageSettingSelected && } {activeTab === 'components' ? ( - + ) : ( - + )}
diff --git a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx index bf4b6b50b4..0862634c77 100644 --- a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx +++ b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx @@ -8,11 +8,13 @@ import Cross from '@/_ui/Icon/solidIcons/Cross'; import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import * as Icons from '@tabler/icons-react'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch, currentPageId, icon }) => { + const { moduleId } = useModuleContext(); const [isExpanded, setIsExpanded] = useState(true); const groupActive = currentPage.pageGroupId === pageGroup?.id; - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const handleToggle = () => { setIsExpanded(!isExpanded); }; @@ -78,8 +80,9 @@ const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch }; const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, currentPage }) => { + const { moduleId } = useModuleContext(); const tree = buildTree(pages); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return (
@@ -123,6 +126,7 @@ const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, cu }; const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeToDarkMode, showDarkModeToggle }) => { + const { moduleId } = useModuleContext(); const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); const license = useStore((state) => state.license); @@ -134,7 +138,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan version: selectedVersionName, env: selectedEnvironmentName, }; - switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams)); + switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), moduleId); }; var styles = { bmBurgerButton: { @@ -189,7 +193,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan const isLicensed = !_.get(license, 'featureAccess.licenseStatus.isExpired', true) && _.get(license, 'featureAccess.licenseStatus.isLicenseValid', false); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return ( <> diff --git a/frontend/src/AppBuilder/Viewer/PageGroup.jsx b/frontend/src/AppBuilder/Viewer/PageGroup.jsx index 1739263fff..9948651c43 100644 --- a/frontend/src/AppBuilder/Viewer/PageGroup.jsx +++ b/frontend/src/AppBuilder/Viewer/PageGroup.jsx @@ -8,6 +8,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import OverflowTooltip from '@/_components/OverflowTooltip'; import cx from 'classnames'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RenderPage = ({ page, currentPageId, switchPageWrapper, labelStyle, computeStyles, darkMode, homePageId }) => { const isHomePage = page.id === homePageId; @@ -139,11 +140,12 @@ const RenderPageGroup = ({ export const RenderPageAndPageGroup = ({ pages, labelStyle, computeStyles, darkMode, switchPageWrapper }) => { // Don't render empty folders if displaying only icons + const { moduleId } = useModuleContext(); const tree = buildTree(pages, !!labelStyle?.label?.hidden); const filteredPages = tree.filter((page) => (!page?.isPageGroup || page.children?.length > 0) && !page?.restricted); const currentPageId = useStore((state) => state.currentPageId); const currentPage = pages.find((page) => page.id === currentPageId); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return (
{/* page.id)}> */} diff --git a/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx b/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx index 633a6dcf0e..e914a9bee4 100644 --- a/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx +++ b/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx @@ -9,8 +9,10 @@ import HeaderActions from '@/AppBuilder/Header/HeaderActions'; import { AppEnvironments } from '@/modules/Appbuilder/components'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useAppType } from '@/AppBuilder/_contexts/ModuleContext'; const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { + const appType = useAppType(); const { setShowUndoRedoBtn, editingVersion } = useStore( (state) => ({ setShowUndoRedoBtn: state?.setShowUndoRedoBtn, @@ -32,14 +34,14 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { Preview settings - {editingVersion && ( + {editingVersion && appType !== 'module' && ( <>
)} - +
@@ -85,13 +87,17 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { {previewNavbar && ( - - - -
- - - + {appType !== 'module' && ( + <> + + + +
+ + + + + )}
{ +export const Viewer = ({ + id: appId, + darkMode, + moduleId = 'canvas', + switchDarkMode, + environmentId, + versionId, + moduleMode = false, +} = {}) => { const DEFAULT_CANVAS_WIDTH = 1292; const { t } = useTranslation(); const [isSidebarPinned, setIsSidebarPinned] = useState(localStorage.getItem('isPagesSidebarPinned') !== 'false'); - useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }); + const appType = useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }, moduleMode); const { isEditorLoading, @@ -41,46 +50,49 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod toggleCurrentLayout, } = useStore( (state) => ({ - isEditorLoading: state.isEditorLoading, - currentMode: state.currentMode, + isEditorLoading: state.loaderStore.modules[moduleId].isEditorLoading, + currentMode: state.modeStore.modules[moduleId].currentMode, currentLayout: state.currentLayout, editingVersion: state.editingVersion, selectedVersion: state.selectedVersion, currentCanvasWidth: state.currentCanvasWidth, - appName: state.app.appName, - homePageId: state?.app.homepageId, - currentPageId: state.currentPageId, + appName: state.appStore.modules[moduleId].app.appName, + homePageId: state.appStore.modules[moduleId].app.homepageId, + currentPageId: state.modules[moduleId].currentPageId, globalSettings: state.globalSettings, - pages: state.modules.canvas.pages, + pages: state.modules[moduleId].pages, modules: state.modules, globalSettingsChanged: state.globalSettingsChanged, pageSettings: state.pageSettings, updateCanvasHeight: state.updateCanvasBottomHeight, - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, setIsViewer: state.setIsViewer, toggleCurrentLayout: state.toggleCurrentLayout, }), shallow ); - const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow); + + const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents(moduleId), shallow); const currentPageComponents = useMemo(() => getCurrentPageComponents, [getCurrentPageComponents]); const changeDarkMode = useStore((state) => state.changeDarkMode); const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); const canvasBgColor = useStore((state) => state.getCanvasBackgroundColor('canvas', darkMode), shallow); const deviceWindowWidth = window.screen.width - 5; + const hideSidebar = moduleMode || isPagesSidebarHidden || appType === 'module'; + const computeCanvasMaxWidth = useCallback(() => { if (globalSettings?.maxCanvasWidth) { return globalSettings.maxCanvasWidth; } if (globalSettings?.canvasMaxWidthType === 'px') { - return (+globalSettings?.canvasMaxWidth || DEFAULT_CANVAS_WIDTH) - (!isPagesSidebarHidden ? 200 : 0); + return (+globalSettings?.canvasMaxWidth || DEFAULT_CANVAS_WIDTH) - (!hideSidebar ? 200 : 0); } if (globalSettings?.canvasMaxWidthType === '%') { return +globalSettings?.canvasMaxWidth + '%'; } return DEFAULT_CANVAS_WIDTH; - }, [globalSettings, isPagesSidebarHidden]); + }, [globalSettings, hideSidebar]); const toggleSidebarPinned = useCallback(() => { const newValue = !isSidebarPinned; @@ -104,8 +116,8 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod }, []); useEffect(() => { - updateCanvasHeight(currentPageComponents); - }, [currentPageComponents, updateCanvasHeight]); + updateCanvasHeight(currentPageComponents, moduleId); + }, [currentPageComponents, moduleId, updateCanvasHeight]); const changeToDarkMode = (newMode) => { switchDarkMode(newMode); @@ -113,16 +125,56 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod useEffect(() => { const isMobileDevice = deviceWindowWidth < 600; toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop'); - setIsViewer(true); + setIsViewer(true, moduleId); return () => { - setIsViewer(false); + setIsViewer(false, moduleId); }; }, []); + const renderHeader = () => { + if (moduleMode) { + return null; + } + + if (currentLayout !== 'mobile') { + return ( + + ); + } + + return ( + <> + {currentLayout === 'mobile' && !isMobilePreviewMode && ( + + )} + + ); + }; + if (isEditorLoading) { return ( -
- +
+ {moduleMode ? : }
); } else if (isMaintenanceOn) { @@ -144,43 +196,18 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod className={cx('viewer wrapper', { 'mobile-layout': currentLayout, 'theme-dark dark-theme': darkMode })} > - - {currentLayout !== 'mobile' && ( - - )} - {currentLayout === 'mobile' && !isMobilePreviewMode && ( - - )} + + {renderHeader()}
- {currentLayout !== 'mobile' && !isPagesSidebarHidden && ( + {currentLayout !== 'mobile' && !hideSidebar && !moduleMode && (
- {currentLayout === 'mobile' && isMobilePreviewMode && ( + {currentLayout === 'mobile' && isMobilePreviewMode && !moduleMode && ( )} - +
- {/* {!licenseValid && isAppLoaded && } */} {isMobilePreviewMode &&
} {isMobilePreviewMode &&
}
@@ -238,11 +270,11 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
-
- +
+ - -
+ +
); } }; diff --git a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx index 5312d60a8a..6a64c6766b 100644 --- a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx +++ b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx @@ -9,6 +9,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { APP_HEADER_HEIGHT } from '../AppCanvas/appCanvasConstants'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { RenderPageAndPageGroup } from './PageGroup'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const ViewerSidebarNavigation = ({ isMobileDevice, @@ -20,10 +21,11 @@ export const ViewerSidebarNavigation = ({ isSidebarPinned, toggleSidebarPinned, }) => { + const { moduleId } = useModuleContext(); const { definition: { styles = {}, properties = {} } = {} } = useStore((state) => state.pageSettings) || {}; const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const license = useStore((state) => state.license); if (isMobileDevice) { @@ -95,7 +97,7 @@ export const ViewerSidebarNavigation = ({ version: selectedVersionName, env: selectedEnvironmentName, }; - switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams)); + switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), moduleId); }; const isLicensed = diff --git a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js index 136ef942c7..21c68480ea 100644 --- a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js +++ b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js @@ -58,6 +58,8 @@ import { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, emailinputConfig, phoneinputConfig, currencyinputConfig, @@ -126,4 +128,6 @@ export const widgets = [ linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, ]; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/index.js b/frontend/src/AppBuilder/WidgetManager/widgets/index.js index e79bee68c2..d387e6230d 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/index.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/index.js @@ -58,6 +58,8 @@ import { kanbanBoardConfig } from './kanbanBoard'; import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; +import { moduleContainerConfig } from './moduleContainer'; +import { moduleViewerConfig } from './moduleViewer'; import { emailinputConfig } from './emailinput'; import { phoneinputConfig } from './phoneinput'; import { currencyinputConfig } from './currencyinput'; @@ -126,4 +128,6 @@ export { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js b/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js new file mode 100644 index 0000000000..bf488095a6 --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js @@ -0,0 +1,36 @@ +export const moduleContainerConfig = { + name: 'ModuleContainer', + displayName: 'Module Container', + description: 'Module Container', + component: 'ModuleContainer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + inputItems: { type: 'array', displayName: 'Input' }, + outputItems: { type: 'array', displayName: 'Output' }, + }, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + inputItems: { value: [] }, + outputItems: { value: [] }, + }, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, +}; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js b/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js new file mode 100644 index 0000000000..b5d2962cde --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js @@ -0,0 +1,30 @@ +export const moduleViewerConfig = { + name: 'ModuleViewer', + displayName: 'Module', + description: 'Module', + component: 'ModuleViewer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: {}, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: {}, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, +}; diff --git a/frontend/src/AppBuilder/Widgets/Kanban/Components/Modal.jsx b/frontend/src/AppBuilder/Widgets/Kanban/Components/Modal.jsx index 3837051886..0acefe2053 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/Components/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/Components/Modal.jsx @@ -6,8 +6,10 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const Modal = function Modal({ darkMode, showModal, setShowModal, kanbanProps, lastSelectedCard }) { + const { moduleId } = useModuleContext(); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const parentRef = useRef(null); const { id, containerProps, component } = kanbanProps; @@ -17,7 +19,7 @@ export const Modal = function Modal({ darkMode, showModal, setShowModal, kanbanP if (Object.keys(diff(lastSelectedCard, prevLastSelectedCard.current)).length > 0) { prevLastSelectedCard.current = lastSelectedCard; // Update the customResolvables with the lastSelectedCard - updateCustomResolvables(`${id}-modal`, [{ cardData: lastSelectedCard }], 'cardData'); + updateCustomResolvables(`${id}-modal`, [{ cardData: lastSelectedCard }], 'cardData', moduleId); } const renderCloseButton = () => { diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx index 8aa7b578ac..90fd261622 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx @@ -26,6 +26,7 @@ import cx from 'classnames'; import { useGridStore } from '@/_stores/gridStore'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const dropAnimation = { sideEffects: defaultDropAnimationSideEffects({ @@ -40,11 +41,12 @@ const dropAnimation = { const TRASH_ID = 'void'; export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { + const { moduleId } = useModuleContext(); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const { properties, fireEvent, setExposedVariable, setExposedVariables, styles } = kanbanProps; const { columnData, cardData, cardWidth, cardHeight, showDeleteButton, enableAddCard } = properties; const { accentColor } = styles; - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const [lastSelectedCard, setLastSelectedCard] = useState({}); // eslint-disable-next-line react-hooks/exhaustive-deps const columnDataAsObj = useMemo(() => convertArrayToObj(columnData), [JSON.stringify(columnData)]); @@ -78,7 +80,8 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { updateCustomResolvables( id, flatCardData.map((d) => ({ cardData: d })), - 'cardData' + 'cardData', + moduleId ); }, []); diff --git a/frontend/src/AppBuilder/Widgets/Listview.jsx b/frontend/src/AppBuilder/Widgets/Listview.jsx index 285f9e5bf9..856ef57e49 100644 --- a/frontend/src/AppBuilder/Widgets/Listview.jsx +++ b/frontend/src/AppBuilder/Widgets/Listview.jsx @@ -9,6 +9,7 @@ import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container'; import { diff } from 'deep-object-diff'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const Listview = function Listview({ id, @@ -21,8 +22,9 @@ export const Listview = function Listview({ darkMode, dataCy, }) { + const { moduleId } = useModuleContext(); const getComponentNameFromId = useStore((state) => state.getComponentNameFromId, shallow); - const childComponents = useStore((state) => state.getChildComponents(id), shallow); + const childComponents = useStore((state) => state.getChildComponents(id, moduleId), shallow); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const fallbackProperties = { height: 100, showBorder: false, data: [] }; const fallbackStyles = { visibility: true, disabledState: false }; @@ -69,7 +71,7 @@ export const Listview = function Listview({ const onOptionChange = useCallback( (optionName, value, componentId, index) => { setChildrenData((prevData) => { - const componentName = getComponentNameFromId(componentId); + const componentName = getComponentNameFromId(componentId, moduleId); const changedData = { [componentName]: { [optionName]: value } }; const existingDataAtIndex = prevData[index] ?? {}; const newDataAtIndex = { @@ -84,13 +86,13 @@ export const Listview = function Listview({ return { ...prevData, ...newChildrenData }; }); }, - [getComponentNameFromId, setChildrenData] + [getComponentNameFromId, setChildrenData, moduleId] ); const onOptionsChange = useCallback( (exposedVariables, componentId, index) => { setChildrenData((prevData) => { - const componentName = getComponentNameFromId(componentId); + const componentName = getComponentNameFromId(componentId, moduleId); const existingDataAtIndex = prevData[index] ?? {}; const changedData = {}; Object.keys(exposedVariables).forEach((key) => { @@ -108,7 +110,7 @@ export const Listview = function Listview({ return { ...prevData, ...newChildrenData }; }); }, - [getComponentNameFromId, setChildrenData] + [getComponentNameFromId, setChildrenData, moduleId] ); function onRecordOrRowClicked(index) { @@ -231,7 +233,7 @@ export const Listview = function Listview({ }; }); // Update the customResolvables with the new listItems - if (listItems.length > 0) updateCustomResolvables(id, listItems, 'listItem'); + if (listItems.length > 0) updateCustomResolvables(id, listItems, 'listItem', moduleId); } return ( diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx index 451c652dce..d3c8e15a9e 100644 --- a/frontend/src/AppBuilder/Widgets/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Modal.jsx @@ -7,6 +7,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { debounce } from 'lodash'; var tinycolor = require('tinycolor2'); +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const Modal = function Modal({ id, @@ -20,6 +21,7 @@ export const Modal = function Modal({ dataCy, height, }) { + const { moduleId } = useModuleContext(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, @@ -48,7 +50,7 @@ export const Modal = function Modal({ const titleAlignment = properties.titleAlignment ?? 'left'; const size = properties.size ?? 'lg'; const [modalWidth, setModalWidth] = useState(); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas); /**** Start - Logic to reset the zIndex of modal control box ****/ diff --git a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx index bfe1431ad0..01c07cf49f 100644 --- a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx +++ b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx @@ -16,6 +16,7 @@ import { createModalStyles } from '@/AppBuilder/Widgets/ModalV2/helpers/stylesFa import { onShowSideEffects, onHideSideEffects } from '@/AppBuilder/Widgets/ModalV2/helpers/sideEffects'; import '@/AppBuilder/Widgets/ModalV2/style.scss'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const ModalV2 = function Modal({ id, @@ -29,6 +30,7 @@ export const ModalV2 = function Modal({ dataCy, height, }) { + const { moduleId } = useModuleContext(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, @@ -56,7 +58,7 @@ export const ModalV2 = function Modal({ const titleAlignment = properties.titleAlignment ?? 'left'; const size = properties.size ?? 'lg'; const setSelectedComponentAsModal = useStore((state) => state.setSelectedComponentAsModal, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const computedModalBodyHeight = getModalBodyHeight(modalHeight, showHeader, showFooter, headerHeight, footerHeight); const headerHeightPx = getModalHeaderHeight(showHeader, headerHeight); diff --git a/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx b/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx index c5f1328aee..7615f9f585 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx @@ -9,9 +9,11 @@ import useTableStore from './_stores/tableStore'; import TableContainer from './_components/TableContainer'; import { transformTableData } from './_utils/transformTableData'; import { usePrevious } from '@dnd-kit/utilities'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const Table = memo( ({ id, componentName, width, height, properties, styles, darkMode, fireEvent, setExposedVariables }) => { + const { moduleId } = useModuleContext(); // get table store functions const initializeComponent = useTableStore((state) => state.initializeComponent, shallow); const removeComponent = useTableStore((state) => state.removeComponent, shallow); @@ -83,7 +85,8 @@ export const Table = memo( firstRowOfTable, autogenerateColumns, columnDeletionHistory, - shouldAutogenerateColumns.current + shouldAutogenerateColumns.current, + moduleId ); shouldAutogenerateColumns.current = false; }, [ @@ -94,6 +97,7 @@ export const Table = memo( setColumnDetails, firstRowOfTable, autogenerateColumns, + moduleId, columnDeletionHistory, ]); diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx index 6ec4f66994..93f0721366 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx @@ -7,6 +7,7 @@ import { filterFunctions } from '../Header/_components/Filter/filterUtils'; import { isArray, debounce } from 'lodash'; import { useMounted } from '@/_hooks/use-mount'; import { usePrevious } from '@dnd-kit/utilities'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; // Component to expose variables & fire events from the table // It might miss some variables which are tightly coupled with the component state export const TableExposedVariables = ({ @@ -19,6 +20,7 @@ export const TableExposedVariables = ({ pageIndex = 1, lastClickedRow, }) => { + const { moduleId } = useModuleContext(); const editedRows = useTableStore((state) => state.getAllEditedRows(id), shallow); const editedFields = useTableStore((state) => state.getAllEditedFields(id), shallow); const addNewRowDetails = useTableStore((state) => state.getAllAddNewRowDetails(id), shallow); @@ -314,7 +316,7 @@ export const TableExposedVariables = ({ // Create debounced function using useRef to persist between renders const debouncedSetProperty = useRef( debounce((sizing) => { - setComponentProperty(id, 'columnSizes', sizing, 'properties'); + setComponentProperty(id, 'columnSizes', sizing, 'properties', 'value', false, moduleId); }, 300) ).current; diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js b/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js index 6663f4f2f9..2ea3a69bc5 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js +++ b/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js @@ -14,7 +14,8 @@ export const createColumnSlice = (set, get) => ({ firstRowOfTable, autogenerateColumnsFlag, columnDeletionHistory, - shouldAutogenerateColumns + shouldAutogenerateColumns, + moduleId = 'canvas' ) => { set( (state) => { @@ -33,7 +34,8 @@ export const createColumnSlice = (set, get) => ({ isDynamicColumnSelected, autogenerateColumnsFlag, columnDeletionHistory, - columnData + columnData, + moduleId ); state.components[id].columnDetails.columnProperties = columnProperties; state.components[id].columnDetails.transformations = get().generateColumnTransformations( @@ -60,7 +62,8 @@ export const createColumnSlice = (set, get) => ({ isDynamicColumnSelected, autogenerateColumnsFlag, columnDeletionHistory, - columnData + columnData, + moduleId ) => { if (autogenerateColumnsFlag) { const setComponentProperty = useStore.getState().setComponentProperty; @@ -75,7 +78,7 @@ export const createColumnSlice = (set, get) => ({ ); if (!isDynamicColumnSelected && !isEqual(existingGeneratedColumn, generatedColumns)) { - setComponentProperty(id, 'columns', generatedColumns, 'properties', 'value', false, 'canvas', { + setComponentProperty(id, 'columns', generatedColumns, 'properties', 'value', false, moduleId, { skipUndoRedo: true, saveAfterAction: true, }); diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 7d378e7d70..5921fd0e34 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -49,6 +49,7 @@ import { EmptyState } from './Components/EmptyState'; import { LoadingState } from './Components/LoadingState'; // eslint-disable-next-line import/no-unresolved import { useVirtualizer } from '@tanstack/react-virtual'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; // utilityForNestedNewRow function is used to construct nested object while adding or updating new row when '.' is present in column key for adding new row const utilityForNestedNewRow = (row) => { @@ -87,11 +88,12 @@ export const Table = React.memo( // events, // setProperty, }) => { - const component = useStore((state) => state.getComponentDefinition(id), shallow); + const { moduleId } = useModuleContext(); + const component = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const exposedNewRows = useStore((state) => state.getExposedValueOfComponent(id)?.newRows || [], shallow); const validateWidget = useStore((state) => state.validateWidget, shallow); const validateDates = useStore((state) => state.validateDates, shallow); - const mode = useStore((state) => state.currentMode); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode); const { color, diff --git a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx index 1c8e64a843..d0f2b96c31 100644 --- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx +++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx @@ -2,16 +2,51 @@ import React, { createContext, useContext } from 'react'; export const ModuleContext = createContext(); -export const ModuleProvider = ({ moduleId, children }) => { - return {children}; +export const ModuleProvider = ({ moduleId, isModuleMode, appType, isModuleEditor, children }) => { + return ( + + {children} + + ); +}; + +export const useModuleContext = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useModuleContext must be used within a ModuleProvider'); + } + return context; }; export const useModuleId = () => { const context = useContext(ModuleContext); - if (!context) { throw new Error('useModuleId must be used within a ModuleProvider'); } - return context; + return context.moduleId; +}; + +export const useIsModuleMode = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useIsModuleMode must be used within a ModuleProvider'); + } + return context.isModuleMode; +}; + +export const useAppType = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useAppType must be used within a ModuleProvider'); + } + return context.appType; +}; + +export const useIsModuleEditor = () => { + const context = useContext(ModuleContext); + if (!context) { + throw new Error('useIsModuleEditor must be used within a ModuleProvider'); + } + return context.isModuleEditor; }; diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 183c5c1cec..f6c00e60a0 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -71,6 +71,9 @@ import { Form } from '@/AppBuilder/Widgets/Form/Form'; import { Modal } from '@/AppBuilder/Widgets/Modal'; import { ModalV2 } from '@/AppBuilder/Widgets/ModalV2/ModalV2'; import { Calendar } from '@/AppBuilder/Widgets/Calendar/Calendar'; + +import { ModuleContainer, ModuleViewer } from '@/modules/Modules/components'; + // import './requestIdleCallbackPolyfill'; export function memoizeFunction(func) { @@ -152,6 +155,8 @@ export const AllComponents = { Form, BoundedBox, ToggleSwitchV2, + ModuleContainer, + ModuleViewer, }; if (isPDFSupported()) { AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF); diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 7b74639870..cd7d18449c 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { appEnvironmentService, appService, + appsService, appVersionService, dataqueryService, datasourceService, @@ -12,7 +13,7 @@ import { } from '@/_services'; import useStore from '@/AppBuilder/_stores/store'; import { useEnvironmentsAndVersionsStore } from '@/_stores/environmentsAndVersionsStore'; -import { camelCase, cloneDeep, isEmpty, kebabCase, mapKeys, rest } from 'lodash'; +import { camelCase, cloneDeep, isEmpty, kebabCase, mapKeys, noop, rest } from 'lodash'; import { usePrevious } from '@dnd-kit/utilities'; import { deepCamelCase } from '@/_helpers/appUtils'; import { useEventActions } from '../_stores/slices/eventsSlice'; @@ -27,6 +28,7 @@ import { distinctUntilChanged } from 'rxjs'; import { baseTheme, convertAllKeysToSnakeCase } from '../_stores/utils'; import { getPreviewQueryParams } from '@/_helpers/routes'; import { useLocation, useMatch, useParams } from 'react-router-dom'; +import { useMounted } from '@/_hooks/use-mount'; import useThemeAccess from './useThemeAccess'; import { handleError } from '@/_helpers/handleAppAccess'; import toast from 'react-hot-toast'; @@ -56,12 +58,22 @@ const normalizeQueryTransformationOptions = (query) => { return query; }; -const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, versionId } = {}) => { +const useAppData = ( + appId, + moduleId, + darkMode, + mode = 'edit', + { environmentId, versionId } = {}, + moduleMode = false +) => { + const mounted = useMounted(); + const initModules = useStore((state) => state.initModules, shallow); + moduleMode && !mounted && initModules(moduleId); const { state } = useLocation(); const [currentSession, setCurrentSession] = useState(); const setEditorLoading = useStore((state) => state.setEditorLoading); const setApp = useStore((state) => state.setApp); - const app = useStore((state) => state.app); + const app = useStore((state) => state.appStore.modules[moduleId].app); const user = useStore((state) => state.user); const setCurrentVersionId = useStore((state) => state.setCurrentVersionId); const currentVersionId = useStore((state) => state.currentVersionId); @@ -79,9 +91,9 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v // const fetchDataSources = useStore((state) => state.fetchDataSources); const fetchGlobalDataSources = useStore((state) => state.fetchGlobalDataSources); const previousVersion = usePrevious(currentVersionId); - const events = useStore((state) => state.eventsSlice.module[moduleId].events); - const pages = useStore((state) => state.modules[moduleId].pages); - const currentPageId = useStore((state) => state.currentPageId); + const events = useStore((state) => state.eventsSlice.module[moduleId]?.events || []); + const pages = useStore((state) => state.modules[moduleId]?.pages || []); + const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); const setResolvedConstants = useStore((state) => state.setResolvedConstants); const setSecrets = useStore((state) => state.setSecrets); const setQueryMapping = useStore((state) => state.setQueryMapping); @@ -106,11 +118,18 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const appMode = useStore((state) => state.globalSettings.appMode); const selectedTheme = useStore((state) => state.globalSettings.theme); const previousEnvironmentId = usePrevious(selectedEnvironment?.id); - const isComponentLayoutReady = useStore((state) => state.isComponentLayoutReady, shallow); + const isComponentLayoutReady = useStore((state) => state.appStore.modules[moduleId].isComponentLayoutReady, shallow); const pageSwitchInProgress = useStore((state) => state.pageSwitchInProgress); const setPageSwitchInProgress = useStore((state) => state.setPageSwitchInProgress); const selectedVersion = useStore((state) => state.selectedVersion); const setIsPublicAccess = useStore((state) => state.setIsPublicAccess); + + const setModulesIsLoading = useStore((state) => state?.setModulesIsLoading ?? noop, shallow); + const setModulesList = useStore((state) => state?.setModulesList ?? noop, shallow); + const setModuleDefinition = useStore((state) => state?.setModuleDefinition ?? noop); + const getModuleDefinition = useStore((state) => state?.getModuleDefinition ?? noop); + const deleteModuleDefinition = useStore((state) => state?.deleteModuleDefinition ?? noop); + const themeAccess = useThemeAccess(); const setConversation = useStore((state) => state.ai?.setConversation); @@ -121,7 +140,8 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const setSelectedSidebarItem = useStore((state) => state.setSelectedSidebarItem); const toggleLeftSidebar = useStore((state) => state.toggleLeftSidebar); const pathParams = useParams(); - const slug = pathParams?.slug; + const slug = moduleMode ? '' : pathParams?.slug; + const licenseStatus = useStore((state) => state.isLicenseValid()); const match = useMatch('/applications/:slug/:pageHandle'); @@ -129,6 +149,14 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const initialLoadRef = useRef(true); + const appTypeRef = useRef(null); + const { isReleasedVersionId } = useStore( + (state) => ({ + isReleasedVersionId: state?.releasedVersionId == state.currentVersionId || state.isVersionReleased, + }), + shallow + ); + const fetchAndInjectCustomStyles = async (isPublicAccess = false) => { try { const head = document.head || document.getElementsByTagName('head')[0]; @@ -153,14 +181,14 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v }; useEffect(() => { - if (pageSwitchInProgress) { + if (pageSwitchInProgress && !moduleMode) { const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === currentPageId); setPageSwitchInProgress(false); setTimeout(() => { handleEvent('onPageLoad', currentPageEvents, {}); }, 0); } - }, [pageSwitchInProgress, currentPageId]); + }, [pageSwitchInProgress, currentPageId, moduleMode]); useEffect(() => { const subscription = authenticationService.currentSession @@ -190,268 +218,317 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v return () => { subscription && subscription.unsubscribe(); }; - }, []); + }, [moduleMode]); useEffect(() => { const exposedTheme = appMode && appMode !== 'auto' ? appMode : localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light'; - setResolvedGlobals('theme', { name: exposedTheme }); - }, [appMode, darkMode]); + setResolvedGlobals('theme', { name: exposedTheme }, moduleId); + }, [appMode, darkMode, moduleId]); useEffect(() => { if (!currentSession) { return; } - const queryParams = getPreviewQueryParams(); - const isPublicAccess = - (currentSession?.load_app && currentSession?.authentication_failed) || (!queryParams.version && mode !== 'edit'); - const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess; let appDataPromise; - if (isPublicAccess) { - appDataPromise = appService.fetchAppBySlug(slug); + const queryParams = moduleMode ? {} : getPreviewQueryParams(); + const isPublicAccess = moduleMode + ? false + : (currentSession?.load_app && currentSession?.authentication_failed) || + (!queryParams.version && mode !== 'edit'); + const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess; + + if (moduleMode) { + const moduleDefinition = getModuleDefinition(appId); + if (moduleDefinition) { + // clean up the module definition from the store + deleteModuleDefinition(appId); + appDataPromise = Promise.resolve(moduleDefinition); + } else { + appDataPromise = appService.fetchApp(appId); + } } else { - appDataPromise = isPreviewForVersion - ? appVersionService.getAppVersionData(appId, versionId) - : appService.fetchApp(appId); + if (isPublicAccess) { + appDataPromise = appService.fetchAppBySlug(slug); + } else { + appDataPromise = isPreviewForVersion + ? appVersionService.getAppVersionData(appId, versionId) + : appService.fetchApp(appId); + } } // 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: viewerEnvironment?.environment?.id, - name: viewerEnvironment?.environment?.name, + id: environmentId, + name: queryParams.env, }; - 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 - ); - - setIsPublicAccess(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; + 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); } } - // 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, mode); - 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 + if (mode === 'edit') { + constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id); + } + // get the constants for specific environment + constantsResp.constants = extractEnvironmentConstantsFromConstantsList( + constantsResp?.constants, + editorEnvironment?.name ); - } - 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(); - }; - }); + !moduleMode && setIsPublicAccess(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; + + appTypeRef.current = appData.type; + + 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, + }, + moduleId + ); + if (!moduleMode) { + 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 && !moduleMode) { + // 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, moduleId); + setCurrentPageId(startingPage.id, moduleId); + setResolvedPageConstants( + { + id: startingPage?.id, + handle: startingPage?.handle, + name: startingPage?.name, + }, + moduleId + ); + setComponentNameIdMapping(moduleId); + updateEventsField('events', appData.events, moduleId); + if (!moduleMode) { + updateFeatureAccess(); + setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); + } + setAppHomePageId(homePageId, moduleId); + if (!moduleMode && appData.modules) { + setModuleDefinition(appData.modules); + } + + 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, moduleId); + if (dataQueries?.length > 0) { + !moduleMode && setSelectedQuery(dataQueries[0]?.id); + initialiseResolvedQuery( + dataQueries.map((query) => query.id), + moduleId + ); + } + 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, moduleId); + setSecrets(orgSecrets, moduleId); + } + setQueryMapping(moduleId); + + setResolvedGlobals('environment', editorEnvironment, moduleId); + setResolvedGlobals('mode', { value: mode }, moduleId); + setResolvedGlobals( + 'currentUser', + { + ...user, + groups: currentSession?.groups, + role: currentSession?.role?.name, + ssoUserInfo: currentSession?.ssoUserInfo, + ...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata) + ? { metadata: currentSession?.currentUser?.metadata } + : {}), + }, + moduleId + ); + setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))), moduleId); + initDependencyGraph(moduleId); + setCurrentMode(mode, moduleId); // TODO: set mode based on the slug/appDef + if ( + !moduleMode && + 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 && !moduleMode) { + 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 + ); + } + if (!moduleMode) { + useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed + updateReleasedVersionId(appData.current_version_id); + } + + setEditorLoading(false, moduleId); + initialLoadRef.current = false; + // only show if app is not created from prompt + if (showWalkthrough && !moduleMode) initEditorWalkThrough(); + !moduleMode && checkAndSetTrueBuildSuggestionsFlag(); + return () => { + document.title = retrieveWhiteLabelText(); + }; + }) + .catch((error) => { + if (moduleMode) { + setEditorLoading(false, moduleId); + toast.error('Error fetching module data'); + } + }); }, [setApp, setEditorLoading, currentSession]); useEffect(() => { if (isComponentLayoutReady) { - runOnLoadQueries().then(() => { + runOnLoadQueries(moduleId).then(() => { let startingPage = pages.find((page) => page.id === currentPageId); const currentPageEvents = events.filter( (event) => event.target === 'page' && event.sourceId === startingPage.id @@ -459,11 +536,18 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v handleEvent('onPageLoad', currentPageEvents, {}); }); } - }, [isComponentLayoutReady]); + }, [isComponentLayoutReady, moduleId]); useEffect(() => { - fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName: app.appName }); - }, [app.appName]); + if (moduleId) return; + fetchAndSetWindowTitle({ + page: pageTitles.EDITOR, + appName: app.appName, + mode: mode, + isReleased: isReleasedVersionId, + licenseStatus: licenseStatus, + }); + }, [app.appName, isReleasedVersionId, licenseStatus, mode, moduleId]); useEffect(() => { if (!themeAccess) return; @@ -476,6 +560,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v }, [darkMode, selectedTheme, themeAccess]); useEffect(() => { + if (moduleMode) return; const exposedTheme = appMode && appMode !== 'auto' ? appMode : localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light'; const isEnvChanged = @@ -483,13 +568,13 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const isVersionChanged = currentVersionId && previousVersion && currentVersionId != previousVersion; if (isEnvChanged || isVersionChanged) { - setEditorLoading(true); + setEditorLoading(true, moduleId); clearSelectedComponents(); if (isEnvChanged) { setEnvironmentLoadingState('loading'); } appVersionService.getAppVersionData(appId, selectedVersion?.id).then(async (appData) => { - cleanUpStore(); + cleanUpStore(false); const { should_freeze_editor } = appData; setIsEditorFreezed(should_freeze_editor); @@ -524,7 +609,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v ); setCurrentPageId(startingPage.id, moduleId); setComponentNameIdMapping(moduleId); - updateEventsField('events', appData.events); + updateEventsField('events', appData.events, moduleId); // const queryData = await dataqueryService.getAll(currentVersionId); if (isEnvChanged) { @@ -550,7 +635,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const queryData = await dataqueryService.getAll(currentVersionId, mode); const dataQueries = queryData.data_queries; dataQueries.forEach((query) => normalizeQueryTransformationOptions(query)); - setQueries(dataQueries); + setQueries(dataQueries, moduleId); if (dataQueries?.length > 0) { setSelectedQuery(dataQueries[0]?.id); initialiseResolvedQuery(dataQueries.map((query) => query.id)); @@ -579,10 +664,27 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v setQueryMapping(moduleId); initDependencyGraph(moduleId); - setEditorLoading(false); + setEditorLoading(false, moduleId); }); } - }, [selectedEnvironment?.id, currentVersionId]); + }, [selectedEnvironment?.id, currentVersionId, moduleMode, moduleId]); + + useEffect(() => { + if (moduleMode) return; + if (mode === 'edit') { + requestIdleCallback( + () => { + appsService.getAll(0, '', '', 'module').then((data) => { + setModulesIsLoading(false); + setModulesList(data.apps); + }); + }, + { timeout: 2000 } + ); // Adding a timeout of 2 seconds as fallback + } + }, [setModulesIsLoading, setModulesList, mode, moduleMode]); + + return appTypeRef.current; }; export default useAppData; diff --git a/frontend/src/AppBuilder/_stores/ast.js b/frontend/src/AppBuilder/_stores/ast.js index 106298588f..8412c3b7b2 100644 --- a/frontend/src/AppBuilder/_stores/ast.js +++ b/frontend/src/AppBuilder/_stores/ast.js @@ -32,7 +32,7 @@ function findExpression(input) { export function extractAndReplaceReferencesFromString(input, componentIdNameMapping = {}, queryIdNameMapping = {}) { // Quick check for relevant keywords const regexForQuickCheck = - /\b(components|queries|globals|variables|page|parameters|secrets|constants)(?:\[\S*|\.\S*|\?\.\S*)/; + /\b(components|queries|globals|variables|page|parameters|secrets|constants|input)(?:\[\S*|\.\S*|\?\.\S*)/; if (!regexForQuickCheck.test(input)) { return { allRefs: [], @@ -41,7 +41,7 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp }; } - const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants)\b/; + const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants|input)\b/; const expressionRegex = /{{(.*?)}}/gs; const results = []; let lastIndex = 0; @@ -312,6 +312,7 @@ function parseExpression(expression, componentIdNameMapping, queryIdNameMapping, variables: true, globals: true, page: true, + input: true, }; walk.simple(ast, { @@ -359,7 +360,7 @@ function parseExpression(expression, componentIdNameMapping, queryIdNameMapping, if ( (rootObject && (rootObject === 'queries' || rootObject === 'components') && path.length >= 3) || - ((rootObject === 'variables' || rootObject === 'globals') && path.length === 2) || + ((rootObject === 'variables' || rootObject === 'globals' || rootObject === 'input') && path.length === 2) || (rootObject === 'page' && path.length === 3) ) { return createReferenceObject(rootObject, path, uuidMappings, componentIdNameMapping, queryIdNameMapping); @@ -386,7 +387,7 @@ function createReferenceObject(entityType, path, uuidMappings, componentIdNameMa const mapping = entityType === 'components' ? componentIdNameMapping : queryIdNameMapping; entityNameOrId = mapping[entityNameOrId] || entityNameOrId; } - } else if (entityType === 'variables' || entityType === 'globals') { + } else if (entityType === 'variables' || entityType === 'globals' || entityType === 'input') { entityKey = path[1]; } else if (entityType === 'page') { entityNameOrId = path[1]; diff --git a/frontend/src/AppBuilder/_stores/slices/DependencyClass.js b/frontend/src/AppBuilder/_stores/slices/DependencyClass.js index 730cb41e12..e263532310 100644 --- a/frontend/src/AppBuilder/_stores/slices/DependencyClass.js +++ b/frontend/src/AppBuilder/_stores/slices/DependencyClass.js @@ -14,7 +14,7 @@ class DependencyGraph { } addDependency(fromPath, toPath, nodeData = '') { - if (fromPath.startsWith('variables.')) { + if (fromPath.startsWith('variables.') || fromPath.startsWith('input.')) { if (!this.hasNode(fromPath)) { const parts = fromPath.split('.'); fromPath = parts.slice(0, 2).join('.'); diff --git a/frontend/src/AppBuilder/_stores/slices/appSlice.js b/frontend/src/AppBuilder/_stores/slices/appSlice.js index ac426cb25f..fdb81e97a9 100644 --- a/frontend/src/AppBuilder/_stores/slices/appSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/appSlice.js @@ -1,4 +1,3 @@ -import { updateCanvasBackground } from '@/_helpers/editorHelpers'; import { appsService, appVersionService } from '@/_services'; import { decimalToHex } from '@/Editor/editorConstants'; import toast from 'react-hot-toast'; @@ -10,8 +9,6 @@ import { replaceEntityReferencesWithIds, baseTheme } from '../utils'; import _ from 'lodash'; const initialState = { - app: {}, - canvasHeight: null, isSaving: false, globalSettings: { theme: baseTheme, @@ -24,19 +21,80 @@ const initialState = { selectedUsers: [], selectedUserGroups: [], }, + appStore: { + modules: { + canvas: { + canvasHeight: null, + app: {}, + isViewer: false, + isComponentLayoutReady: false, + }, + }, + }, }; export const createAppSlice = (set, get) => ({ ...initialState, - setIsViewer: (isViewer) => set(() => ({ isViewer }), false, 'setIsViewer'), - setApp: (app) => set(() => ({ app }), false, 'setApp'), - setAppName: (name) => set((state) => ({ app: { ...state.app, appName: name } }), false, 'setAppName'), - setAppHomePageId: (homePageId) => set((state) => ({ app: { ...state.app, homePageId } }), false, 'setAppHomePageId'), - setIsComponentLayoutReady: (isReady) => - set(() => ({ isComponentLayoutReady: isReady }), false, 'setIsComponentLayoutReady'), - setCanvasHeight: (canvasHeight) => set({ canvasHeight }, false, 'setCanvasHeight'), - updateCanvasBottomHeight: (components) => { - const { currentLayout, currentMode, setCanvasHeight } = get(); + initializeAppSlice: (moduleId) => { + set( + (state) => { + state.appStore.modules[moduleId] = { ...initialState.appStore.modules.canvas }; + }, + false, + 'initializeAppSlice' + ); + }, + setIsViewer: (isViewer, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].isViewer = isViewer; + }, + false, + 'setIsViewer' + ), + setApp: (app, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].app = app; + }, + false, + 'setApp' + ), + setAppName: (name, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].app.appName = name; + }, + false, + 'setAppName' + ), + setAppHomePageId: (homePageId, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].app.homePageId = homePageId; + }, + false, + 'setAppHomePageId' + ), + setIsComponentLayoutReady: (isReady, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].isComponentLayoutReady = isReady; + }, + false, + 'setIsComponentLayoutReady' + ), + setCanvasHeight: (canvasHeight, moduleId = 'canvas') => + set( + (state) => { + state.appStore.modules[moduleId].canvasHeight = canvasHeight; + }, + false, + 'setCanvasHeight' + ), + updateCanvasBottomHeight: (components, moduleId = 'canvas') => { + const { currentLayout, getCurrentMode, setCanvasHeight } = get(); + const currentMode = getCurrentMode(moduleId); const maxHeight = Object.values(components).reduce((max, component) => { const layout = component?.layouts?.[currentLayout]; if (!layout) { @@ -47,23 +105,25 @@ export const createAppSlice = (set, get) => ({ }, 0); const bottomPadding = currentMode === 'view' ? 100 : 300; const frameHeight = currentMode === 'view' ? 45 : 85; - setCanvasHeight(`max(100vh - ${frameHeight}px, ${maxHeight + bottomPadding}px)`); + setCanvasHeight(`max(100vh - ${frameHeight}px, ${maxHeight + bottomPadding}px)`, moduleId); }, - setIsAppSaving: (isSaving) => { + setIsAppSaving: (isSaving, moduleId = 'canvas') => { set( (state) => { - state.app.isSaving = isSaving; + state.appStore.modules[moduleId].app.isSaving = isSaving; }, false, 'setIsAppSaving' ); }, setGlobalSettings: (globalSettings) => set(() => ({ globalSettings }), false, 'setGlobalSettings'), - toggleAppMaintenance: () => { - const { isMaintenanceOn, appId } = get().app; + toggleAppMaintenance: (moduleId = 'canvas') => { + const { isMaintenanceOn, appId } = get().appStore.modules[moduleId].app; appsService.setMaintenance(appId, !isMaintenanceOn).then(() => { - set((state) => ({ app: { ...state.app, isMaintenanceOn: !isMaintenanceOn } })); + set((state) => { + state.appStore.modules[moduleId].app.isMaintenanceOn = !isMaintenanceOn; + }); if (isMaintenanceOn) { toast.success('Application is on maintenance.'); } else { @@ -71,9 +131,9 @@ export const createAppSlice = (set, get) => ({ } }); }, - globalSettingsChanged: async (options) => { - const componentNameIdMapping = get().modules.canvas.componentNameIdMapping; - const queryNameIdMapping = get().modules.canvas.queryNameIdMapping; + globalSettingsChanged: async (options, moduleId = 'canvas') => { + const componentNameIdMapping = get().modules[moduleId].componentNameIdMapping; + const queryNameIdMapping = get().modules[moduleId].queryNameIdMapping; for (const [key, value] of Object.entries(options)) { if (value?.[1]?.a == undefined) { options[key] = value; @@ -84,10 +144,10 @@ export const createAppSlice = (set, get) => ({ } // Replace entity references with ids if present const newOptions = replaceEntityReferencesWithIds(options, componentNameIdMapping, queryNameIdMapping); - const { app, currentVersionId, currentPageId } = get(); + const { appStore, currentVersionId, currentPageId } = get(); try { const res = await appVersionService.autoSaveApp( - app.appId, + appStore.modules[moduleId].app.appId, currentVersionId, { globalSettings: newOptions }, 'global_settings', @@ -100,7 +160,7 @@ export const createAppSlice = (set, get) => ({ console.error('Error updating page:', error); } }, - switchPage: (pageId, handle, queryParams = [], isBackOrForward = false) => { + switchPage: (pageId, handle, queryParams = [], moduleId = 'canvas', isBackOrForward = false) => { get().debugger.resetUnreadErrorCount(); // reset stores if (get().pageSwitchInProgress) { @@ -118,24 +178,24 @@ export const createAppSlice = (set, get) => ({ setResolvedGlobals, setResolvedPageConstants, setPageSwitchInProgress, - currentMode, license, modules: { canvas: { pages }, }, + getCurrentMode, } = get(); - const isPreview = currentMode !== 'edit'; + const isPreview = getCurrentMode(moduleId) !== 'edit'; //!TODO clear all queued tasks cleanUpStore(true); - setCurrentPageId(pageId, 'canvas'); - setComponentNameIdMapping('canvas'); - setQueryMapping('canvas'); + setCurrentPageId(pageId, moduleId); + setComponentNameIdMapping(moduleId); + setQueryMapping(moduleId); const isLicenseValid = !_.get(license, 'featureAccess.licenseStatus.isExpired', true) && _.get(license, 'featureAccess.licenseStatus.isLicenseValid', false); - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const filteredQueryParams = queryParams.filter(([key, value]) => { if (!value) return false; if (key === 'env' && isLicenseValid) return false; @@ -143,7 +203,7 @@ export const createAppSlice = (set, get) => ({ }); const queryParamsString = filteredQueryParams.map(([key, value]) => `${key}=${value}`).join('&'); - const slug = get().app.slug; + const slug = get().appStore.modules[moduleId].app.slug; if (!isBackOrForward) { navigate( @@ -159,11 +219,14 @@ export const createAppSlice = (set, get) => ({ } const newPage = pages.find((p) => p.id === pageId); - setResolvedPageConstants({ - id: newPage?.id, - handle: newPage?.handle, - name: newPage?.name, - }); + setResolvedPageConstants( + { + id: newPage?.id, + handle: newPage?.handle, + name: newPage?.name, + }, + moduleId + ); setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(queryParamsString)))); initDependencyGraph('canvas'); setPageSwitchInProgress(true); @@ -171,8 +234,9 @@ export const createAppSlice = (set, get) => ({ setPageSwitchInProgress: (isInProgress) => set(() => ({ pageSwitchInProgress: isInProgress }), false, 'setPageSwitchInProgress'), - cleanUpStore: (isPageSwitch = false) => { - get().resetUndoRedoStack(); + cleanUpStore: (isPageSwitch = false, moduleId) => { + const { resetUndoRedoStack, initModules } = get(); + resetUndoRedoStack(); set((state) => { state.modules.canvas.componentNameIdMapping = {}; state.selectedComponents = []; @@ -188,27 +252,34 @@ export const createAppSlice = (set, get) => ({ state.resolvedStore.modules.canvas.customResolvables = {}; state.resolvedStore.modules.canvas.exposedValues.components = {}; state.resolvedStore.modules.canvas.exposedValues.page.variables = {}; + // initModules(moduleId); }); }, - setSlug: (slug) => { + setSlug: (slug, moduleId = 'canvas') => { set( (state) => { - state.app.slug = slug; + state.appStore.modules[moduleId].app.slug = slug; }, false, 'setSlug' ); }, - setIsPublic: (isPublic) => { + setIsPublic: (isPublic, moduleId = 'canvas') => { set( (state) => { - state.app.isPublic = isPublic; + state.appStore.modules[moduleId].app.isPublic = isPublic; }, false, 'setIsPublic' ); }, + getAppId: (moduleId = 'canvas') => { + return get().appStore.modules[moduleId].app.appId; + }, + getHomePageId: (moduleId = 'canvas') => { + return get().appStore.modules[moduleId].app.homePageId; + }, updateIsTJDarkMode: (newMode) => set({ isTJDarkMode: newMode }, false, 'updateIsTJDarkMode'), setSelectedUserGroups: (groups) => set((state) => { diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index b500a4d912..9075dbede2 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -6,6 +6,7 @@ import { checkSubstringRegex, hasArrayNotation, parsePropertyPath, + resolveCode, } from '@/AppBuilder/_stores/utils'; import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; @@ -29,19 +30,19 @@ import { findHighestLevelofSelection } from '@/AppBuilder/AppCanvas/Grid/gridUti const initialState = { modules: { canvas: { + currentPageId: null, + currentPageIndex: 0, pages: [], componentNameIdMapping: {}, queryNameIdMapping: {}, queryIdNameMapping: {}, + currentPageHandle: null, }, }, - currentPageId: null, - currentPageIndex: 0, containerChildrenMapping: { canvas: [], }, selectedComponents: [], - currentPageHandle: null, showWidgetDeleteConfirmation: false, focusedParentId: null, modalsOpenOnCanvas: [], @@ -50,6 +51,17 @@ const initialState = { export const createComponentsSlice = (set, get) => ({ ...initialState, + initializeComponentsSlice: (moduleId) => { + set( + (state) => { + state.modules[moduleId] = { ...initialState.modules.canvas }; + state.containerChildrenMapping[moduleId] = []; + }, + false, + 'initializeComponentsSlice' + ); + }, + setPages: (pages = [], moduleId = 'canvas') => { set( (state) => { @@ -70,16 +82,16 @@ export const createComponentsSlice = (set, get) => ({ ); }, - setCurrentPageId: (id, moduleId) => + setCurrentPageId: (id, moduleId = 'canvas') => set( (state) => { - const currentPageIndex = state.modules.canvas.pages.findIndex((page) => page.id === id); + const currentPageIndex = state.modules[moduleId].pages.findIndex((page) => page.id === id); const currentPageComponents = state.modules[moduleId].pages[currentPageIndex]?.components || {}; - state.currentPageIndex = currentPageIndex; - state.currentPageId = id; - state.containerChildrenMapping = { canvas: [] }; + state.modules[moduleId].currentPageIndex = currentPageIndex; + state.modules[moduleId].currentPageId = id; + state.containerChildrenMapping[moduleId] = []; Object.entries(currentPageComponents).forEach(([componentId, component]) => { - const parentId = component.component.parent || 'canvas'; + const parentId = component.component.parent || moduleId; if (!state.containerChildrenMapping[parentId]) { state.containerChildrenMapping[parentId] = []; } @@ -89,10 +101,10 @@ export const createComponentsSlice = (set, get) => ({ false, 'setCurrentPageId' ), - setCurrentPageHandle: (handle) => { + setCurrentPageHandle: (handle, moduleId = 'canvas') => { set( (state) => { - state.currentPageHandle = handle; + state.modules[moduleId].currentPageHandle = handle; }, false, 'setCurrentPageHandle' @@ -149,7 +161,7 @@ export const createComponentsSlice = (set, get) => ({ }, setComponentNameIdMapping: (moduleId = 'canvas') => { - const components = get().getCurrentPageComponents(); + const components = get().getCurrentPageComponents(moduleId); set( (state) => { Object.entries(components).forEach(([componentId, component]) => { @@ -162,12 +174,13 @@ export const createComponentsSlice = (set, get) => ({ }, setComponentName: (componentId, newName, moduleId = 'canvas') => { - const { renameComponentNameIdMapping, saveComponentChanges } = get(); + const { renameComponentNameIdMapping, saveComponentChanges, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); let oldName = ''; set( (state) => { - oldName = state.modules[moduleId].pages[state.currentPageIndex].components[componentId].component.name; - state.modules[moduleId].pages[state.currentPageIndex].components[componentId].component.name = newName; + oldName = state.modules[moduleId].pages[currentPageIndex].components[componentId].component.name; + state.modules[moduleId].pages[currentPageIndex].components[componentId].component.name = newName; }, false, 'setComponentName' @@ -177,7 +190,7 @@ export const createComponentsSlice = (set, get) => ({ [componentId]: { component: { name: newName } }, }; - saveComponentChanges(diff, 'components', 'update'); + saveComponentChanges(diff, 'components', 'update', moduleId); renameComponentNameIdMapping(oldName, newName, moduleId); }, @@ -227,7 +240,15 @@ export const createComponentsSlice = (set, get) => ({ get().checkAndSetTrueBuildSuggestionsFlag(); }, - generateDependencyGraphForRefs: (allRefs, key, paramType, property, unResolvedValue, isUpdate = false) => { + generateDependencyGraphForRefs: ( + allRefs, + key, + paramType, + property, + unResolvedValue, + isUpdate = false, + moduleId = 'canvas' + ) => { const { addDependency, updateDependency } = get(); if (allRefs.length !== 0) { allRefs.forEach(({ entityType, entityNameOrId, entityKey }, index) => { @@ -236,9 +257,9 @@ export const createComponentsSlice = (set, get) => ({ : `${entityType}.${entityKey}`; const propertyPath = paramType === undefined ? `others.${key}` : `components.${key}.${paramType}.${property}`; if (isUpdate && index === 0) { - updateDependency(propertyValue, propertyPath, unResolvedValue); + updateDependency(propertyValue, propertyPath, unResolvedValue, moduleId); } else { - addDependency(propertyValue, propertyPath, unResolvedValue); + addDependency(propertyValue, propertyPath, unResolvedValue, moduleId); } }); } @@ -330,7 +351,7 @@ export const createComponentsSlice = (set, get) => ({ const length = Object.keys(customResolvables).length; if (length === 0) { const resolvedValue = shouldResolve - ? resolveDynamicValues(value, getAllExposedValues(), customResolvables, false, []) + ? resolveDynamicValues(value, getAllExposedValues(moduleId), customResolvables, false, []) : value; if (!componentResolvedValues[componentId] || Object.keys(componentResolvedValues[componentId]).length === 0) { componentResolvedValues[componentId] = index === null ? deepClone(DEFAULT_COMPONENT_STRUCTURE) : []; @@ -372,7 +393,7 @@ export const createComponentsSlice = (set, get) => ({ // Loop all the index and set the resolved value for (let i = 0; i < length; i++) { const resolvedValue = shouldResolve - ? resolveDynamicValues(value, getAllExposedValues(), customResolvables[i], false, []) + ? resolveDynamicValues(value, getAllExposedValues(moduleId), customResolvables[i], false, []) : value; if (!componentResolvedValues[componentId] || Object.keys(componentResolvedValues[componentId]).length === 0) { componentResolvedValues[componentId] = []; @@ -425,14 +446,14 @@ export const createComponentsSlice = (set, get) => ({ const length = Object.keys(customResolvables).length; if (length === 0) { const resolvedValue = shouldResolve - ? resolveDynamicValues(unResolvedValue, getAllExposedValues(), customResolvables, false, []) + ? resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), customResolvables, false, []) : value; setResolvedComponentByProperty(componentId, paramType, property, resolvedValue, index, moduleId); } else { // Loop all the index and set the resolved value for (let i = 0; i < length; i++) { const resolvedValue = shouldResolve - ? resolveDynamicValues(unResolvedValue, getAllExposedValues(), customResolvables[i], false, []) + ? resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), customResolvables[i], false, []) : value; setResolvedComponentByProperty(componentId, paramType, property, resolvedValue, i, moduleId); } @@ -638,7 +659,15 @@ export const createComponentsSlice = (set, get) => ({ ); lodashSet(updatedPropertyValue, [index, ...keys], updatedValue); if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); + generateDependencyGraphForRefs( + allRefs, + componentId, + paramType, + propertyWithArrayValue, + unResolvedValue, + false, + moduleId + ); } }); } else { @@ -654,9 +683,16 @@ export const createComponentsSlice = (set, get) => ({ moduleId ); updatedPropertyValue[index] = updatedValue; - console.log('updatedPropertyValue', updatedPropertyValue); if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); + generateDependencyGraphForRefs( + allRefs, + componentId, + paramType, + propertyWithArrayValue, + unResolvedValue, + false, + moduleId + ); } } }); @@ -673,7 +709,7 @@ export const createComponentsSlice = (set, get) => ({ moduleId ); if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue); + generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, false, moduleId); } return { allRefs, unResolvedValue, updatedValue }; } @@ -711,7 +747,7 @@ export const createComponentsSlice = (set, get) => ({ addToDependencyGraph: (moduleId = 'canvas', componentId, component) => { const { updateDependencyGraphAndResolvedValues, getResolvedComponent } = get(); //TODO: Replace with object of component types - let resolvedComponentValues = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) }; + let resolvedComponentValues = { [componentId]: deepClone(getResolvedComponent(componentId, null, moduleId) ?? {}) }; const componentType = componentTypes.find((comp) => component.component === comp.component); ['properties', 'general', 'generalStyles', 'others', 'styles', 'validation'].forEach((key) => { updateDependencyGraphAndResolvedValues( @@ -728,7 +764,7 @@ export const createComponentsSlice = (set, get) => ({ initDependencyGraph: (moduleId) => { const { getCurrentPageComponents, addToDependencyGraph, setResolvedComponents, resolveOthers } = get(); - const components = getCurrentPageComponents(); + const components = getCurrentPageComponents(moduleId); //TODO: Replace with object of component types let resolvedComponentValues = {}; @@ -764,9 +800,9 @@ export const createComponentsSlice = (set, get) => ({ get().modules[moduleId].componentNameIdMapping, get().modules[moduleId].queryNameIdMapping ); - const resolvedValue = resolveDynamicValues(valueWithBrackets, getAllExposedValues(), {}, false, []); + const resolvedValue = resolveDynamicValues(valueWithBrackets, getAllExposedValues(moduleId), {}, false, []); resolvedValues[key] = resolvedValue; - generateDependencyGraphForRefs(allRefs, key, undefined, undefined, valueWithBrackets, isUpdate); + generateDependencyGraphForRefs(allRefs, key, undefined, undefined, valueWithBrackets, isUpdate, moduleId); } else { resolvedValues[key] = item; } @@ -797,7 +833,9 @@ export const createComponentsSlice = (set, get) => ({ canAddToParent, getComponentNameFromId, deleteComponentNameIdMapping, + getCurrentPageId, } = get(); + const currentPageId = getCurrentPageId(moduleId); // This is made into a promise to wait for the saveComponentChanges to complete so that the caller can await it return new Promise((resolve) => { if ( @@ -811,7 +849,7 @@ export const createComponentsSlice = (set, get) => ({ } const newComponents = componentDefinitions.reduce((acc, componentDefinition) => { const currentComponents = { - ...getCurrentPageComponents(), + ...getCurrentPageComponents(moduleId), ...Object.fromEntries(acc.map((component) => [component.id, component])), }; const componentName = @@ -865,7 +903,7 @@ export const createComponentsSlice = (set, get) => ({ if (!state.containerChildrenMapping[parentId].includes(newComponent.id)) { state.containerChildrenMapping[parentId].push(newComponent.id); } - const page = state.modules[moduleId].pages.find((page) => page.id === state.currentPageId); + const page = state.modules[moduleId].pages.find((page) => page.id === currentPageId); page.components[newComponent.id] = newComponent; }, skipUndoRedo), false, @@ -876,7 +914,7 @@ export const createComponentsSlice = (set, get) => ({ get().setSelectedComponents(selectedComponents.map((component) => component.id)); if (saveAfterAction) { - saveComponentChanges(diff, 'components', 'create') + saveComponentChanges(diff, 'components', 'create', moduleId) .then(() => { resolve(); // Resolve the promise after all operations are complete }) @@ -901,7 +939,11 @@ export const createComponentsSlice = (set, get) => ({ selectedComponents, deleteComponentNameIdMapping, removeNode, + getCurrentPageId, + checkIfComponentIsModule, + clearModuleFromStore, } = get(); + const currentPageId = getCurrentPageId(moduleId); const appEvents = get().eventsSlice.getModuleEvents(moduleId); const componentNames = []; const _selectedComponents = selected?.length ? selected : selectedComponents; @@ -910,7 +952,7 @@ export const createComponentsSlice = (set, get) => ({ withUndoRedo((state) => { const toDeleteComponents = []; const toDeleteEvents = []; - const allComponents = getCurrentPageComponents(); + const allComponents = getCurrentPageComponents(moduleId); const findAllChildComponents = (componentId) => { if (!toDeleteComponents.includes(componentId)) { @@ -931,7 +973,7 @@ export const createComponentsSlice = (set, get) => ({ findAllChildComponents(componentId); }); - const page = state.modules?.canvas?.pages.find((page) => page.id === state.currentPageId); + const page = state.modules?.[moduleId]?.pages.find((page) => page.id === currentPageId); const resolvedComponents = state.resolvedStore.modules?.[moduleId]?.components; const componentsExposedValues = state.resolvedStore.modules?.[moduleId]?.exposedValues.components; @@ -943,12 +985,18 @@ export const createComponentsSlice = (set, get) => ({ ); }); + if (checkIfComponentIsModule(id, moduleId)) { + clearModuleFromStore(id); + } + // Remove the container itself if it's a container if (state.containerChildrenMapping[id]) { delete state.containerChildrenMapping[id]; } if (state.containerChildrenMapping?.canvas?.includes(id)) { - state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter((wid) => wid !== id); + state.containerChildrenMapping[moduleId].canvas = state.containerChildrenMapping[moduleId].filter( + (wid) => wid !== id + ); } componentNames.push(page.components[id]?.component?.name); const eventsToRemove = appEvents.filter((event) => event.sourceId === id).map((event) => event.id); @@ -957,7 +1005,7 @@ export const createComponentsSlice = (set, get) => ({ delete resolvedComponents[id]; // Remove the component from the resolved store delete componentsExposedValues[id]; // Remove the component from the exposed values state.selectedComponents = []; // Empty the selected components - removeNode(`components.${id}`); + removeNode(`components.${id}`, moduleId); state.showWidgetDeleteConfirmation = false; // Set it to false always }); @@ -965,7 +1013,7 @@ export const createComponentsSlice = (set, get) => ({ state.eventsSlice.module[moduleId].events = filteredEvents; if (saveAfterAction) { - saveComponentChanges(toDeleteComponents, 'components', 'delete') + saveComponentChanges(toDeleteComponents, 'components', 'delete', moduleId) .then(() => { get().multiplayer.broadcastUpdates({ selectedComponents: _selectedComponents }, 'components', 'delete'); // Show delete toast message @@ -991,7 +1039,7 @@ export const createComponentsSlice = (set, get) => ({ 'deleteComponents' ); componentNames.forEach((componentName) => { - deleteComponentNameIdMapping(componentName); + deleteComponentNameIdMapping(componentName, moduleId); }); }, @@ -1040,12 +1088,14 @@ export const createComponentsSlice = (set, get) => ({ getComponentDefinition, currentLayout, checkValueAndResolve, + getCurrentPageIndex, } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); let hasParentChanged = false; let oldParentId; set( withUndoRedo((state) => { - const page = state.modules[moduleId].pages[state.currentPageIndex]; + const page = state.modules[moduleId].pages[currentPageIndex]; if (page) { // ============ Component layout update logic ============ Object.entries(componentLayouts).forEach(([componentId, layout]) => { @@ -1069,8 +1119,8 @@ export const createComponentsSlice = (set, get) => ({ state.containerChildrenMapping[oldParentId] = state.containerChildrenMapping[oldParentId].filter( (id) => id !== componentId ); - } else if (state.containerChildrenMapping.canvas.includes(componentId)) { - state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter( + } else if (state.containerChildrenMapping[moduleId].includes(componentId)) { + state.containerChildrenMapping[moduleId] = state.containerChildrenMapping[moduleId].filter( (id) => id !== componentId ); } @@ -1082,7 +1132,7 @@ export const createComponentsSlice = (set, get) => ({ } state.containerChildrenMapping[newParentId].push(componentId); } else { - state.containerChildrenMapping.canvas.push(componentId); + state.containerChildrenMapping[moduleId].push(componentId); } } // ============ Parent update logic ends ============ @@ -1149,7 +1199,7 @@ export const createComponentsSlice = (set, get) => ({ }, {}); if (saveAfterAction) { - saveComponentChanges(diff, 'components/layout', 'update'); + saveComponentChanges(diff, 'components/layout', 'update', moduleId); get().multiplayer.broadcastUpdates(diff, 'components/layout', 'update'); } }, @@ -1165,7 +1215,7 @@ export const createComponentsSlice = (set, get) => ({ { skipUndoRedo = false, saveAfterAction = true } = {} ) => { const { - currentPageIndex, + getCurrentPageIndex, saveComponentChanges, withUndoRedo, updateResolvedValues, @@ -1176,12 +1226,14 @@ export const createComponentsSlice = (set, get) => ({ checkValueAndResolve, getResolvedComponent, setResolvedComponent, + getCurrentMode, } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); const { component } = getComponentDefinition(componentId, moduleId); const oldValue = component.definition[paramType][property]; if (Array.isArray(oldValue?.value)) { - const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) }; + const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId, null, moduleId) ?? {}) }; resolvedComponent[componentId][paramType][property] = []; const { updatedValue } = checkValueAndResolve( @@ -1221,8 +1273,8 @@ export const createComponentsSlice = (set, get) => ({ }; if (saveAfterAction) { - const currentMode = get().currentMode; - if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update'); + const currentMode = getCurrentMode(moduleId); + if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update', moduleId); get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update'); } @@ -1273,18 +1325,18 @@ export const createComponentsSlice = (set, get) => ({ }; if (saveAfterAction) { - const currentMode = get().currentMode; - if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update'); + const currentMode = getCurrentMode(moduleId); + if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update', moduleId); get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update'); } if (attr !== 'value' || skipResolve) return; if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true); + generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true, moduleId); } else { const propertyPath = `components.${componentId}.${paramType}.${property}`; - removeDependency(propertyPath, true); + removeDependency(propertyPath, true, moduleId); } }, @@ -1317,8 +1369,8 @@ export const createComponentsSlice = (set, get) => ({ state.containerChildrenMapping[oldParentId] = state.containerChildrenMapping[oldParentId].filter( (id) => id !== componentId ); - } else if (state.containerChildrenMapping.canvas.includes(componentId)) { - state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter( + } else if (state.containerChildrenMapping[moduleId].includes(componentId)) { + state.containerChildrenMapping[moduleId] = state.containerChildrenMapping[moduleId].filter( (id) => id !== componentId ); } @@ -1330,7 +1382,7 @@ export const createComponentsSlice = (set, get) => ({ } state.containerChildrenMapping[newParentId].push(componentId); } else { - state.containerChildrenMapping.canvas.push(componentId); + state.containerChildrenMapping[moduleId].push(componentId); } }, skipUndoRedo), false, @@ -1381,7 +1433,7 @@ export const createComponentsSlice = (set, get) => ({ }; if (saveAfterAction) { - saveComponentChanges(diff, 'components', 'update'); + saveComponentChanges(diff, 'components', 'update', moduleId); get().multiplayer.broadcastUpdates({ componentId, newParentId }, 'components', 'parent'); } }, @@ -1411,21 +1463,21 @@ export const createComponentsSlice = (set, get) => ({ setFocusedParentId: (parentId) => { set((state) => { state.focusedParentId = parentId; - }); + }), + false, + { type: 'setFocusedParentId', payload: { parentId } }; }, - saveComponentChanges: (diff, type, operation) => { + saveComponentChanges: (diff, type, operation, moduleId = 'canvas') => { set( (state) => { - state.app.isSaving = true; + state.appStore.modules[moduleId].app.isSaving = true; }, false, 'setAppSavingChanges' ); - const { - app: { appId }, - currentVersionId, - currentPageId, - } = get(); + const { getAppId, currentVersionId, getCurrentPageId } = get(); + const appId = getAppId(moduleId); + const currentPageId = getCurrentPageId(moduleId); return new Promise((resolve) => { appVersionService @@ -1449,7 +1501,7 @@ export const createComponentsSlice = (set, get) => ({ .finally(() => { set( (state) => { - state.app.isSaving = false; + state.appStore.modules[moduleId].app.isSaving = false; }, false, 'setAppSavingChanges' @@ -1475,7 +1527,9 @@ export const createComponentsSlice = (set, get) => ({ }, turnOffAutoComputeLayout: async (moduleId = 'canvas') => { - const { app, currentPageId, currentVersionId } = get(); + const { appStore, getCurrentPageId, currentVersionId } = get(); + const app = appStore.modules[moduleId].app; + const currentPageId = getCurrentPageId(moduleId); set( (state) => { state.modules[moduleId].pages[state.currentPageIndex].autoComputeLayout = false; @@ -1492,38 +1546,44 @@ export const createComponentsSlice = (set, get) => ({ }); }, - getCurrentPageId: () => get().currentPageId, + getCurrentPageId: (moduleId = 'canvas') => get().modules[moduleId].currentPageId, + getCurrentPageIndex: (moduleId = 'canvas') => get().modules[moduleId].currentPageIndex, - getComponentsFromAllPages: () => { + getComponentsFromAllPages: (moduleId = 'canvas') => { const { modules } = get(); return Object.fromEntries( - modules.canvas.pages.flatMap((page) => + modules[moduleId].pages.flatMap((page) => Object.entries(page.components).map(([id, { component }]) => [id, component.name]) ) ); }, - getCurrentPageComponents: () => { - const { modules, currentPageId } = get(); - const currentPageIndex = modules.canvas.pages.findIndex((page) => page.id === currentPageId); - return modules.canvas.pages[currentPageIndex]?.components || []; + getCurrentPageComponents: (moduleId = 'canvas') => { + const { modules, getCurrentPageId } = get(); + const currentPageId = getCurrentPageId(moduleId); + const currentPageIndex = modules[moduleId].pages.findIndex((page) => page.id === currentPageId); + return modules[moduleId].pages[currentPageIndex]?.components || []; }, - getCurrentPageComponentIds: () => { - const { pages, currentPageId, modules } = get(); - const currentPageIndex = modules.canvas.pages.findIndex((page) => page.id === currentPageId); + getCurrentPageComponentIds: (moduleId = 'canvas') => { + const { pages, getCurrentPageId, modules } = get(); + const currentPageId = getCurrentPageId(moduleId); + const currentPageIndex = modules[moduleId].pages.findIndex((page) => page.id === currentPageId); return Object.keys(pages[currentPageIndex]?.components || {}); }, getCurrentPage: (moduleId = 'canvas') => { - const { modules, currentPageId } = get(); + const { modules, getCurrentPageId } = get(); + const currentPageId = getCurrentPageId(moduleId); const currentPage = modules[moduleId].pages.find((page) => page.id === currentPageId); return currentPage; }, // Get the component definition from the component id getComponentDefinition: (componentId, moduleId = 'canvas') => { - const currentPage = get().modules[moduleId].pages.find((page) => page.id === get().currentPageId); + const currentPage = get().modules[moduleId].pages.find((page) => page.id === get().getCurrentPageId(moduleId)); + // if (componentId === 'd78554b8-2af0-4add-9d7d-0032bb4c90ce') + // console.trace('here--- getComponentDefinition--- ', componentId, moduleId, currentPage?.components[componentId]); return currentPage?.components[componentId]; }, @@ -1533,24 +1593,26 @@ export const createComponentsSlice = (set, get) => ({ }, // Get the component name from the component id getComponentNameFromId: (componentId, moduleId = 'canvas') => { - const { modules, currentPageIndex } = get(); + const { modules, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); return modules[moduleId].pages[currentPageIndex]?.components[componentId]?.component.name; }, getComponentTypeFromId: (componentId, moduleId = 'canvas') => { - const { modules, currentPageIndex } = get(); + const { modules, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); return modules[moduleId].pages[currentPageIndex]?.components[componentId]?.component.component; }, getComponentNameIdMapping: (moduleId = 'canvas') => { const { modules } = get(); return modules[moduleId].componentNameIdMapping; }, - getComponentIdNameMapping: () => { + getComponentIdNameMapping: (moduleId = 'canvas') => { const { getComponentNameIdMapping } = get(); - return Object.fromEntries(Object.entries(getComponentNameIdMapping()).map(([name, id]) => [id, name])); + return Object.fromEntries(Object.entries(getComponentNameIdMapping(moduleId)).map(([name, id]) => [id, name])); }, - getSelectedComponentsDefinition: () => { + getSelectedComponentsDefinition: (moduleId = 'canvas') => { const { selectedComponents, getCurrentPageComponents } = get(); - const allComponents = getCurrentPageComponents(); + const allComponents = getCurrentPageComponents(moduleId); const _selected = []; for (let componentId of selectedComponents) { const component = { @@ -1574,13 +1636,17 @@ export const createComponentsSlice = (set, get) => ({ const { modules } = get(); return modules[moduleId].queryIdNameMapping; }, + getQueryIdFromName: (queryName, moduleId = 'canvas') => { + const { modules } = get(); + return modules[moduleId].queryNameIdMapping[queryName]; + }, getContainerChildrenMapping: (id) => { const { containerChildrenMapping } = get(); return containerChildrenMapping[id] || []; }, getChildComponents: (parentId, moduleId = 'canvas') => { const { getCurrentPageComponents } = get(); - const allComponents = getCurrentPageComponents(); + const allComponents = getCurrentPageComponents(moduleId); const childComponents = Object.entries(allComponents) .filter(([_, component]) => component.component.parent === parentId) .reduce((acc, [id, component]) => { @@ -1609,8 +1675,8 @@ export const createComponentsSlice = (set, get) => ({ } else { const [entityType, entityId, type, ...keys] = dependency.split('.'); const key = keys.join('.'); - const unResolvedValue = getNodeData(dependency); - const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(), {}, false, []); + const unResolvedValue = getNodeData(dependency, moduleId); + const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), {}, false, []); if (type === undefined) { set( @@ -1624,7 +1690,7 @@ export const createComponentsSlice = (set, get) => ({ } else { const shouldValidate = entityType === 'components' && entityId; const validatedValue = shouldValidate - ? get().debugger.validateProperty(entityId, type, key, resolvedValue) + ? get().debugger.validateProperty(entityId, type, key, resolvedValue, moduleId) : resolvedValue; // logic to handle the key like options[0].visible. It will resolve the visible directly and update the resolved store @@ -1639,7 +1705,7 @@ export const createComponentsSlice = (set, get) => ({ lodashSet( state.resolvedStore.modules[moduleId][entityType][entityId], ['properties', 'shouldRender'], - (getResolvedComponent(entityId)?.['properties']?.['shouldRender'] ?? 0) + 1 + (getResolvedComponent(entityId, null, moduleId)?.['properties']?.['shouldRender'] ?? 0) + 1 ); }, false, @@ -1688,24 +1754,24 @@ export const createComponentsSlice = (set, get) => ({ } }, - getParentIdFromDependency: (dependency) => { + getParentIdFromDependency: (dependency, moduleId = 'canvas') => { const { getComponentDefinition } = get(); const componentId = dependency.split('.')[1]; - const component = getComponentDefinition(componentId); + const component = getComponentDefinition(componentId, moduleId); return component?.component?.parent; }, updateChildComponentResolvedValues: (dependency, path, length, moduleId = 'canvas') => { const { getCustomResolvables, getNodeData, getAllExposedValues, getParentIdFromDependency } = get(); const [entityType, entityId, type, key] = dependency.split('.'); - const parentId = getParentIdFromDependency(dependency); - const unResolvedValue = getNodeData(dependency); + const parentId = getParentIdFromDependency(dependency, moduleId); + const unResolvedValue = getNodeData(dependency, moduleId); // Loop through the customResolvables and update the resolved value for (let i = 0; i < length; i++) { const resolvedValue = resolveDynamicValues( unResolvedValue, - getAllExposedValues(), + getAllExposedValues(moduleId), getCustomResolvables(parentId, i, moduleId), // passing the parent ID and index to get the custom resolvables of the child false, [] @@ -1713,7 +1779,7 @@ export const createComponentsSlice = (set, get) => ({ // If the index is not in the resolved store then add it with first index data const shouldValidate = entityType === 'components' && entityId; const validatedValue = shouldValidate - ? get().debugger.validateProperty(entityId, type, key, resolvedValue) + ? get().debugger.validateProperty(entityId, type, key, resolvedValue, moduleId) : resolvedValue; set( @@ -1736,7 +1802,8 @@ export const createComponentsSlice = (set, get) => ({ getParentComponentType: (parentId, moduleId) => { if (!parentId) return null; - const { modules, currentPageIndex } = get(); + const { modules, getCurrentPageIndex } = get(); + const currentPageIndex = getCurrentPageIndex(moduleId); // Remove the tab id or any other details from the parent id (ie, -modal, -calendar, -0 from parentId) const parentUUID = parentId.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] || parentId; const component = modules[moduleId].pages[currentPageIndex].components[parentUUID]; @@ -1833,8 +1900,8 @@ export const createComponentsSlice = (set, get) => ({ return match; // Return the original match if no mapping is found }); }, - calculateMoveableBoxHeightWithId: (componentId, currentLayout, stylesDefinition) => { - const componentDefinition = get().getComponentDefinition(componentId); + calculateMoveableBoxHeightWithId: (componentId, currentLayout, stylesDefinition, moduleId = 'canvas') => { + const componentDefinition = get().getComponentDefinition(componentId, moduleId); const layoutData = componentDefinition?.layouts?.[currentLayout]; const componentType = componentDefinition?.component?.component; const label = componentDefinition?.component?.definition?.properties?.label; @@ -1862,8 +1929,8 @@ export const createComponentsSlice = (set, get) => ({ } const { alignment = { value: null }, width = { value: null }, auto = { value: null } } = stylesDefinition ?? {}; const resolvedLabel = label?.value?.length ?? 0; - const resolvedWidth = resolveDynamicValues(width?.value + '', getAllExposedValues()) ?? 0; - const resolvedAuto = resolveDynamicValues(auto?.value + '', getAllExposedValues()) ?? false; + const resolvedWidth = resolveDynamicValues(width?.value + '', getAllExposedValues(moduleId)) ?? 0; + const resolvedAuto = resolveDynamicValues(auto?.value + '', getAllExposedValues(moduleId)) ?? false; const resolvedAlignment = alignment.value === 'top' || alignment.value === 'side' @@ -1896,6 +1963,8 @@ export const createComponentsSlice = (set, get) => ({ state.modalsOpenOnCanvas = newModalOpenOnCanvas; }); }, + checkIfComponentIsModule: (componentId, moduleId = 'canvas') => + get().getComponentDefinition(componentId, moduleId)?.component?.component === 'ModuleViewer', updateContainerAutoHeight: (componentId) => { if ( !componentId || diff --git a/frontend/src/AppBuilder/_stores/slices/createSelectors.js b/frontend/src/AppBuilder/_stores/slices/createSelectors.js deleted file mode 100644 index 50fd299f4e..0000000000 --- a/frontend/src/AppBuilder/_stores/slices/createSelectors.js +++ /dev/null @@ -1,13 +0,0 @@ -// import { useStore } from 'zustand'; - -// const createSelectors = (_store) => { -// const store = _store; -// store.use = {}; -// for (const k of Object.keys(store.getState())) { -// store.use[k] = () => useStore(_store, (s) => s[k]); -// } - -// return store; -// }; - -// export { createSelectors }; diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index f9b2badccd..34bea1bf84 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -22,10 +22,20 @@ const initialState = { }; export const createDataQuerySlice = (set, get) => ({ + initializeDataQuerySlice: (moduleId = 'canvas') => { + set( + (state) => { + state.dataQuery.queries.modules[moduleId] = []; + }, + false, + 'initializeDataQuerySlice' + ); + }, dataQuery: { ...initialState, - checkExistingQueryName: (newName) => get().dataQuery.queries.modules.canvas.some((query) => query.name === newName), - getCurrentModuleQueries: (moduleId) => get().dataQuery.queries.modules[moduleId], + checkExistingQueryName: (newName, moduleId = 'canvas') => + get().dataQuery.queries.modules[moduleId].some((query) => query.name === newName), + getCurrentModuleQueries: (moduleId = 'canvas') => get().dataQuery.queries.modules[moduleId], setQueries: (queries, moduleId = 'canvas') => { set( (state) => { @@ -49,7 +59,7 @@ export const createDataQuerySlice = (set, get) => ({ }, createDataQuery: (selectedDataSource, shouldRunQuery, customOptions = {}, moduleId = 'canvas') => { const appVersionId = get().currentVersionId; - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const { options: defaultOptions, name } = getDefaultOptions(selectedDataSource); const options = { ...defaultOptions, ...customOptions }; const kind = selectedDataSource.kind; @@ -101,7 +111,7 @@ export const createDataQuerySlice = (set, get) => ({ return query; }); }); - setSelectedQuery(data.id, data); + setSelectedQuery(data.id, moduleId); if (shouldRunQuery) setQueryToBeRun(data); /** Checks if there is an API call cached. If yes execute it */ @@ -121,12 +131,16 @@ export const createDataQuerySlice = (set, get) => ({ get().addNewQueryMapping(data.id, data.name, moduleId); //! we need default value in store so that query can be resolved if referenced from other entity - get().setResolvedQuery(data.id, { - isLoading: false, - data: [], - rawData: [], - id: data.id, - }); + get().setResolvedQuery( + data.id, + { + isLoading: false, + data: [], + rawData: [], + id: data.id, + }, + moduleId + ); }) .catch((error) => { set((state) => { @@ -220,8 +234,8 @@ export const createDataQuerySlice = (set, get) => ({ }) .finally(() => setIsAppSaving(false)); - get().removeNode(`queries.${queryId}`); - get().updateDependencyValues(`queries.${queryId}`); + get().removeNode(`queries.${queryId}`, moduleId); + get().updateDependencyValues(`queries.${queryId}`, moduleId); }, duplicateQuery: (id, appId, moduleId = 'canvas') => { set((state) => { @@ -265,16 +279,20 @@ export const createDataQuerySlice = (set, get) => ({ ...state.dataQuery.queries.modules[moduleId], ]; }); - setSelectedQuery(data.id, { ...data, data_source_id: queryToClone.data_source_id }); + setSelectedQuery(data.id, moduleId); get().addNewQueryMapping(data.id, data.name, moduleId); //! we need default value in store so that query can be resolved if referenced from other entity - get().setResolvedQuery(data.id, { - isLoading: false, - data: [], - rawData: [], - id: data.id, - }); + get().setResolvedQuery( + data.id, + { + isLoading: false, + data: [], + rawData: [], + id: data.id, + }, + moduleId + ); const events = getEventsByComponentsId(queryToClone.id); @@ -451,15 +469,26 @@ export const createDataQuerySlice = (set, get) => ({ }); }); }, 500), - runOnLoadQueries: async () => { - const queries = get().dataQuery.queries.modules.canvas; + runOnLoadQueries: async (moduleId = 'canvas') => { + const queries = get().dataQuery.queries.modules[moduleId]; try { for (const query of queries) { if ( (query.options?.runOnPageLoad || query.options?.run_on_page_load) && (query.restricted || isQueryRunnable(query)) ) { - await get().queryPanel.runQuery(query.id, query.name, undefined, undefined, {}, false, true, 'canvas'); + await get().queryPanel.runQuery( + query.id, + query.name, + undefined, + undefined, + {}, + undefined, + undefined, + false, + true, + moduleId + ); } } return Promise.resolve(); diff --git a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js index f4bab3d4ee..912ee596ae 100644 --- a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js @@ -32,7 +32,7 @@ export const createDebuggerSlice = (set, get) => ({ log: (log) => { set( (state) => { - log.page = get().currentPageId; + log.page = get().getCurrentPageId('canvas'); state.debugger.logs.unshift(log); if (log.logLevel === 'error') state.debugger.unreadErrorCount++; }, @@ -44,7 +44,7 @@ export const createDebuggerSlice = (set, get) => ({ logMultiple: (logs) => { set( (state) => { - state.debugger.logs.push(...logs.map((log) => ({ ...log, page: get().currentPageId }))); + state.debugger.logs.push(...logs.map((log) => ({ ...log, page: get().getCurrentPageId('canvas') }))); state.debugger.unreadErrorCount += logs.length; }, false, @@ -84,20 +84,20 @@ export const createDebuggerSlice = (set, get) => ({ return transformedStyles; }, - validateComponents: (components) => { + validateComponents: (components, moduleId = 'canvas') => { const validateComponent = get().debugger.validateComponent; const entries = Object.entries(components).map(([id, component]) => { // If component is an array, validate each component in the array and return the array if (Array.isArray(component)) { - return [id, component.map((c) => validateComponent(id, c))]; + return [id, component.map((c) => validateComponent(id, c, moduleId))]; } - return [id, validateComponent(id, component)]; + return [id, validateComponent(id, component, moduleId)]; }); return Object.fromEntries(entries); }, - validateComponent: (id, component) => { - const componentDefinition = get().getComponentDefinition(id); + validateComponent: (id, component, moduleId = 'canvas') => { + const componentDefinition = get().getComponentDefinition(id, moduleId); const componentName = componentDefinition.component.name; const componentType = componentDefinition.component.component; const componentMeta = componentTypeDefinitionMap[componentType]; @@ -135,7 +135,7 @@ export const createDebuggerSlice = (set, get) => ({ }; const logs = allErrors.map((error) => ({ - page: get().currentPageId, + page: get().getCurrentPageId('canvas'), type: 'component', kind: 'component', key: `${componentName} - ${error.property}`, @@ -158,10 +158,10 @@ export const createDebuggerSlice = (set, get) => ({ return newComponent; }, - validateProperty: (componentId, type, property, value) => { + validateProperty: (componentId, type, property, value, moduleId = 'canvas') => { const log = get().debugger.log; - const componentDefinition = get().getComponentDefinition(componentId); + const componentDefinition = get().getComponentDefinition(componentId, moduleId); const componentName = componentDefinition.component.name; const componentType = componentDefinition.component.component; const componentMeta = componentTypeDefinitionMap[componentType]; @@ -179,7 +179,7 @@ export const createDebuggerSlice = (set, get) => ({ if (valid === false) { log({ - page: get().currentPageId, + page: get().getCurrentPageId('canvas'), type: 'component', kind: 'component', key: `${componentName} - ${componentMeta[type][property]?.displayName}`, diff --git a/frontend/src/AppBuilder/_stores/slices/dependencySlice.js b/frontend/src/AppBuilder/_stores/slices/dependencySlice.js index 8bea44819d..66fc0d1b16 100644 --- a/frontend/src/AppBuilder/_stores/slices/dependencySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dependencySlice.js @@ -12,48 +12,61 @@ const initialState = { export const createDependencySlice = (set, get) => ({ ...initialState, + initializeDependencySlice: (moduleId) => { + set( + (state) => { + state.dependencyGraph.modules[moduleId] = { + graph: new DependencyGraph(), + }; + }, + false, + 'initializeDependencySlice' + ); + }, - addDependency: (fromPath, toPath, nodeData) => { - if (!get().checkIfDependencyExists(fromPath, toPath)) { + addDependency: (fromPath, toPath, nodeData, moduleId = 'canvas') => { + if (!get().checkIfDependencyExists(fromPath, toPath, moduleId)) { set((state) => { - state.dependencyGraph.modules.canvas.graph.addDependency(fromPath, toPath, nodeData); + state.dependencyGraph.modules[moduleId].graph.addDependency(fromPath, toPath, nodeData); return { ...state }; }); } }, - updateDependency: (newFromPath, toPath, nodeData) => + updateDependency: (newFromPath, toPath, nodeData, moduleId = 'canvas') => set((state) => { - state.dependencyGraph.modules.canvas.graph.updateDependency(newFromPath, toPath, nodeData); + state.dependencyGraph.modules[moduleId].graph.updateDependency(newFromPath, toPath, nodeData); return { ...state }; }), - removeDependency: (toPath, clearToPath = false) => + removeDependency: (toPath, clearToPath = false, moduleId = 'canvas') => set((state) => { - state.dependencyGraph.modules.canvas.graph.removeDependency(toPath, clearToPath); + state.dependencyGraph.modules[moduleId].graph.removeDependency(toPath, clearToPath); return { ...state }; }), - removeNode: (path) => + removeNode: (path, moduleId = 'canvas') => set((state) => { - state.dependencyGraph.modules.canvas.graph.removeNode(path); + state.dependencyGraph.modules[moduleId].graph.removeNode(path); return { ...state }; }), - getNodeData: (path) => get().dependencyGraph.modules.canvas.graph.getNodeData(path), + getNodeData: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getNodeData(path), - getDependencies: (path) => get().dependencyGraph.modules.canvas.graph.getDependencies(path), + getDependencies: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getDependencies(path), - getDirectDependencies: (path) => get().dependencyGraph.modules.canvas.graph.getDirectDependencies(path), + getDirectDependencies: (path, moduleId = 'canvas') => + get().dependencyGraph.modules[moduleId].graph.getDirectDependencies(path), - getDependents: (path) => get().dependencyGraph.modules.canvas.graph.getDependents(path), + getDependents: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getDependents(path), - getDirectDependents: (path) => get().dependencyGraph.modules.canvas.graph.getDirectDependents(path), + getDirectDependents: (path, moduleId = 'canvas') => + get().dependencyGraph.modules[moduleId].graph.getDirectDependents(path), - getOverallOrder: () => get().dependencyGraph.modules.canvas.graph.getOverallOrder(), + getOverallOrder: (moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getOverallOrder(), - checkIfDependencyExists: (fromPath, toPath) => { - const dependencies = get().getDependencies(fromPath); + checkIfDependencyExists: (fromPath, toPath, moduleId = 'canvas') => { + const dependencies = get().getDependencies(fromPath, moduleId); return dependencies.includes(toPath); }, }); diff --git a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js index 1edd3994c5..1077a22608 100644 --- a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js @@ -243,7 +243,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({ const versionIsAvailableInEnvironment = environment?.priority <= get().currentAppVersionEnvironment?.priority; if (!versionIsAvailableInEnvironment) { - const appId = useStore.getState().app.appId; + const { appId } = useStore.getState().appStore.modules.canvas.app; const response = await appEnvironmentService.postEnvironmentChangedAction({ appId, editorEnvironmentId: environmentId, @@ -285,7 +285,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({ promoteAppVersionAction: async (versionId, onSuccess, onFailure) => { try { - const appId = useStore.getState().app.appId; // Correct way to access appId + const { appId } = useStore.getState().appStore.modules.canvas.app; const response = await appVersionService.promoteEnvironment(appId, versionId, get().selectedEnvironment.id); set((state) => ({ diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js index f93f64b1c5..5991f093f8 100644 --- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js @@ -6,7 +6,6 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { dfs } from '@/_stores/handleReferenceTransactions'; import { isQueryRunnable, isValidUUID, serializeNestedObjectToQueryParams } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; -import { handleLowPriorityWork } from '@/AppBuilder/_helpers/editorHelpers'; import _ from 'lodash'; import { logoutAction } from '@/AppBuilder/_utils/auth'; import { copyToClipboard } from '@/_helpers/appUtils'; @@ -59,8 +58,8 @@ export const useEventActions = (moduleId = 'canvas') => { ); const memoizedUpdateEventsField = useCallback( - (field, value) => updateEventsField(field, value, moduleId), - [updateEventsField, moduleId] + (field, value, moduleId) => updateEventsField(field, value, moduleId), + [updateEventsField] ); return { @@ -72,6 +71,17 @@ export const useEventActions = (moduleId = 'canvas') => { }; export const createEventsSlice = (set, get) => ({ + initializeEventsSlice: (moduleId) => { + set( + (state) => { + state.eventsSlice.module[moduleId] = { + ...initialState.module.canvas, + }; + }, + false, + 'initializeEventsSlice' + ); + }, eventsSlice: { ...initialState, setEvents: (events, moduleId = 'canvas') => { @@ -98,25 +108,17 @@ export const createEventsSlice = (set, get) => ({ ); }, fireEvent: (eventName, id, moduleId, customResolvables, options) => { - const { eventsSlice } = get(); - const { - handleEvent, - isEditorLoading, - module: { - [moduleId]: { events }, - }, - } = eventsSlice; + const { eventsSlice, getCurrentMode, getEditorLoading } = get(); + const { handleEvent } = eventsSlice; + const events = get().eventsSlice.module[moduleId].events; const componentEvents = events.filter((event) => event.sourceId === id); - const mode = get().currentMode; - if (isEditorLoading) return; - // if (mode === 'edit' && eventName === 'onClick') { - // onComponentClick(id, component); - // } + const mode = getCurrentMode(moduleId); + if (getEditorLoading(moduleId)) return; handleEvent( eventName, componentEvents, { ...options, customVariables: { ...customResolvables } }, - 'canvas', + moduleId, mode ); }, @@ -129,7 +131,7 @@ export const createEventsSlice = (set, get) => ({ }, } = eventsSlice; const componentEvents = events.filter((event) => event.sourceId === id); - executeActionsForEventId('onClick', componentEvents, mode); + executeActionsForEventId('onClick', componentEvents, mode, moduleId); }, addEvent: (event, moduleId = 'canvas') => set((state) => { @@ -164,46 +166,46 @@ export const createEventsSlice = (set, get) => ({ createAppVersionEventHandlers: async (event, moduleId) => { // get().actions.setIsSaving(true); // set({ eventsCreatedLoader: true }); - get().eventsSlice.updateEventsField('eventsCreatedLoader', true); - const appId = get().app.appId; + get().eventsSlice.updateEventsField('eventsCreatedLoader', true, moduleId); + const appId = get().appStore.modules[moduleId].app.appId; const versionId = get().currentVersionId; appVersionService .createAppVersionEventHandler(appId, versionId, event) .then((response) => { - get().eventsSlice.updateEventsField('eventsCreatedLoader', false); - get().eventsSlice.addEvent(response); + get().eventsSlice.updateEventsField('eventsCreatedLoader', false, moduleId); + get().eventsSlice.addEvent(response, moduleId); }) .catch((err) => { - get().eventsSlice.updateEventsField('eventsCreatedLoader', false); + get().eventsSlice.updateEventsField('eventsCreatedLoader', false, moduleId); toast.error(err?.error || 'An error occurred while creating the event handler'); }); }, deleteAppVersionEventHandler: async (eventId, index, moduleId = 'canvas') => { - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const versionId = get().currentVersionId; - get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', index); + get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', index, moduleId); const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId); - get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', null); + get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', null, moduleId); if (response?.affected === 1) { - get().eventsSlice.removeEvent(eventId); + get().eventsSlice.removeEvent(eventId, moduleId); } }, updateAppVersionEventHandlers: async (events, updateType = 'update', param, moduleId = 'canvas') => { if (param === 'actionId') { - get().eventsSlice.updateEventsField('actionsUpdatedLoader', true); + get().eventsSlice.updateEventsField('actionsUpdatedLoader', true, moduleId); } if (param === 'eventId') { - get().eventsSlice.updateEventsField('eventsUpdatedLoader', true); + get().eventsSlice.updateEventsField('eventsUpdatedLoader', true, moduleId); } const componentNameIdMapping = get().modules['canvas'].componentNameIdMapping; const queryNameIdMapping = get().modules['canvas'].queryNameIdMapping; //! Revisit this - const appId = get().app.appId; + const appId = get().appStore.modules[moduleId].app.appId; const versionId = get().currentVersionId; const newEvents = replaceEntityReferencesWithIds(events, componentNameIdMapping, queryNameIdMapping); const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, newEvents, updateType); - get().eventsSlice.updateEventsField('actionsUpdatedLoader', false); - get().eventsSlice.updateEventsField('eventsUpdatedLoader', false); + get().eventsSlice.updateEventsField('actionsUpdatedLoader', false, moduleId); + get().eventsSlice.updateEventsField('eventsUpdatedLoader', false, moduleId); set((state) => { const eventsInState = state.eventsSlice.getModuleEvents('canvas'); const newEvents = eventsInState.map((event) => { @@ -266,19 +268,19 @@ export const createEventsSlice = (set, get) => ({ return foundEvent && foundEvent.name === eventName; }); try { - return get().eventsSlice.onEvent(eventName, filteredEvents, options, mode); + return get().eventsSlice.onEvent(eventName, filteredEvents, options, mode, moduleId); } catch (error) { console.error(error); } }, - onEvent: async (eventName, events, options = {}, mode = 'edit') => { + onEvent: async (eventName, events, options = {}, mode = 'edit', moduleId = 'canvas') => { const executeActionsForEventId = get().eventsSlice.executeActionsForEventId; const customVariables = options?.customVariables ?? {}; const { setExposedValue } = get(); if (eventName === 'onPageLoad') { // for onPageLoad events, we need to execute the actions after the page is loaded - executeActionsForEventId('onPageLoad', events, mode, customVariables); + executeActionsForEventId('onPageLoad', events, mode, customVariables, moduleId); } if (eventName === 'onTrigger') { const { queryPanel, dataQuery } = get(); @@ -287,7 +289,7 @@ export const createEventsSlice = (set, get) => ({ const { queryName, parameters } = options; const queryId = queries.filter((query) => query.name === queryName && isQueryRunnable(query))?.[0]?.id; if (!queryId) return; - runQuery(queryId, queryName, true, mode, parameters); + runQuery(queryId, queryName, true, mode, parameters, undefined, undefined, false, false, moduleId); } if (eventName === 'onTableActionButtonClicked') { const { action, tableActionEvents } = options; @@ -296,7 +298,7 @@ export const createEventsSlice = (set, get) => ({ if (action && executeableActions) { for (const event of executeableActions) { if (event?.event?.actionId) { - await get().eventsSlice.executeAction(event.event, mode, customVariables); + await get().eventsSlice.executeAction(event.event, mode, customVariables, moduleId); } } } else { @@ -310,7 +312,7 @@ export const createEventsSlice = (set, get) => ({ if (column && tableColumnEvents) { for (const event of tableColumnEvents) { if (event?.event?.actionId) { - await get().eventsSlice.executeAction(event.event, mode, customVariables); + await get().eventsSlice.executeAction(event.event, mode, customVariables, moduleId); } } } else { @@ -321,13 +323,13 @@ export const createEventsSlice = (set, get) => ({ if (eventName === 'onCalendarEventSelect') { const { id, calendarEvent } = options; setExposedValue(id, 'selectedEvent', calendarEvent); - executeActionsForEventId('onCalendarEventSelect', events, mode, customVariables); + executeActionsForEventId('onCalendarEventSelect', events, mode, customVariables, moduleId); } if (eventName === 'onCalendarSlotSelect') { const { id, selectedSlots } = options; setExposedValue(id, 'selectedSlots', selectedSlots); - executeActionsForEventId('onCalendarSlotSelect', events, mode, customVariables); + executeActionsForEventId('onCalendarSlotSelect', events, mode, customVariables, moduleId); } if ( @@ -385,31 +387,31 @@ export const createEventsSlice = (set, get) => ({ 'onTableDataDownload', ].includes(eventName) ) { - executeActionsForEventId(eventName, events, mode, customVariables); + executeActionsForEventId(eventName, events, mode, customVariables, moduleId); } if (eventName === 'onBulkUpdate') { - await executeActionsForEventId(eventName, events, mode, customVariables); + await executeActionsForEventId(eventName, events, mode, customVariables, moduleId); } if (['onDataQuerySuccess', 'onDataQueryFailure'].includes(eventName)) { if (!events || !Array.isArray(events) || events.length === 0) return; - await executeActionsForEventId(eventName, events, mode, customVariables); + await executeActionsForEventId(eventName, events, mode, customVariables, moduleId); } }, - executeActionsForEventId: async (eventId, events = [], mode, customVariables) => { + executeActionsForEventId: async (eventId, events = [], mode, customVariables, moduleId = 'canvas') => { if (!events || !Array.isArray(events) || events.length === 0) return; const filteredEvents = events ?.filter((event) => event?.event.eventId === eventId) ?.sort((a, b) => a.index - b.index); for (const event of filteredEvents) { - await get().eventsSlice.executeAction(event, mode, customVariables); + await get().eventsSlice.executeAction(event, mode, customVariables, moduleId); } }, logError(errorType, errorKind, error, eventObj = '', options = {}, logLevel = 'error') { const { event = eventObj } = eventObj; const pages = get().modules.canvas.pages; - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); const currentPage = pages.find((page) => page.id === currentPageId); const componentIdMapping = get().modules['canvas'].componentNameIdMapping; const componentName = Object.keys(componentIdMapping).find( @@ -479,12 +481,12 @@ export const createEventsSlice = (set, get) => ({ timestamp: moment().toISOString(), }); }, - executeAction: debounce(async (eventObj, mode, customVariables = {}) => { + executeAction: debounce(async (eventObj, mode, customVariables = {}, moduleId = 'canvas') => { const { event = eventObj } = eventObj; const { getExposedValueOfComponent, getResolvedValue } = get(); if (event?.runOnlyIf) { - const shouldRun = getResolvedValue(event.runOnlyIf, customVariables); + const shouldRun = getResolvedValue(event.runOnlyIf, customVariables, moduleId); if (!shouldRun) { return false; } @@ -494,7 +496,8 @@ export const createEventsSlice = (set, get) => ({ //! TODO run only if conditions switch (event.actionId) { case 'show-alert': { - let message = getResolvedValue(event.message, customVariables); + let message = getResolvedValue(event.message, customVariables, moduleId); + if (typeof message === 'object') message = JSON.stringify(message); switch (event.alertType) { @@ -552,16 +555,32 @@ export const createEventsSlice = (set, get) => ({ if (!queryId && !queryName) { throw new Error('No query selected'); } + // Check and replace the module input dummy queries with the linked query id + /* Logic starts here */ + const moduleInputDummyQueries = get()?.getModuleInputDummyQueries?.() || {}; + let updatedQueryId = queryId, + updatedQueryName = queryName, + updatedModuleId = moduleId; + if (moduleInputDummyQueries[queryId]) { + updatedQueryId = + get().resolvedStore.modules[moduleId].exposedValues.input[moduleInputDummyQueries[queryId]]?.id; + updatedModuleId = 'canvas'; // Updating the moduleId to canvas as the query is a module input query which will be present on canvas + } + /* Logic ends here */ + + if (!updatedQueryId) { + throw new Error('No query selected'); + } const resolvedParams = {}; if (params) { Object.keys(params).map( - (param) => (resolvedParams[param] = getResolvedValue(params[param], undefined)) + (param) => (resolvedParams[param] = getResolvedValue(params[param], undefined, moduleId)) ); } // !Todo tackle confirm query part once done return get().queryPanel.runQuery( - queryId, - queryName, + updatedQueryId, + updatedQueryName, undefined, undefined, resolvedParams, @@ -569,7 +588,7 @@ export const createEventsSlice = (set, get) => ({ eventId, false, false, - 'canvas' + updatedModuleId ); } catch (error) { get().eventsSlice.logError('run_query', 'run-query', error, eventObj, { @@ -583,7 +602,7 @@ export const createEventsSlice = (set, get) => ({ } case 'open-webpage': { //! if resolvecode default value should be the value itself not empty string ... Ask KAVIN - const resolvedValue = getResolvedValue(event.url, customVariables); + const resolvedValue = getResolvedValue(event.url, customVariables, moduleId); // const url = resolveReferences(event.url, undefined, customVariables); window.open(resolvedValue, event?.windowTarget === 'newTab' ? '_blank' : '_self'); return Promise.resolve(); @@ -593,7 +612,7 @@ export const createEventsSlice = (set, get) => ({ if (!event.slug) { throw new Error('No application slug provided'); } - const resolvedValue = getResolvedValue(event.slug, customVariables); + const resolvedValue = getResolvedValue(event.slug, customVariables, moduleId); const slug = resolvedValue; const queryParams = event.queryParams?.reduce( (result, queryParam) => ({ @@ -631,23 +650,23 @@ export const createEventsSlice = (set, get) => ({ case 'close-modal': return get().eventsSlice.showModal(event.modal, false, eventObj); case 'copy-to-clipboard': { - const contentToCopy = getResolvedValue(event.contentToCopy, customVariables); + const contentToCopy = getResolvedValue(event.contentToCopy, customVariables, moduleId); copyToClipboard(contentToCopy); return Promise.resolve(); } case 'set-localstorage-value': { - const key = getResolvedValue(event.key, customVariables); - const value = getResolvedValue(event.value, customVariables); + const key = getResolvedValue(event.key, customVariables, moduleId); + const value = getResolvedValue(event.value, customVariables, moduleId); localStorage.setItem(key, value); return Promise.resolve(); } case 'generate-file': { // const fileType = event.fileType; - const data = getResolvedValue(event.data, customVariables) || []; - const fileName = getResolvedValue(event.fileName, customVariables) || 'data.txt'; - const fileType = getResolvedValue(event.fileType, customVariables) || 'csv'; + const data = getResolvedValue(event.data, customVariables, moduleId) || []; + const fileName = getResolvedValue(event.fileName, customVariables, moduleId) || 'data.txt'; + const fileType = getResolvedValue(event.fileType, customVariables, moduleId) || 'csv'; const fileData = { csv: generateCSV, plaintext: (plaintext) => plaintext, @@ -658,15 +677,22 @@ export const createEventsSlice = (set, get) => ({ } case 'set-table-page': { - get().eventsSlice.setTablePageIndex(event.table, getResolvedValue(event.pageIndex), eventObj); + get().eventsSlice.setTablePageIndex( + event.table, + getResolvedValue(event.pageIndex, undefined, moduleId), + eventObj + ); break; } case 'set-custom-variable': { const { setVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - const value = getResolvedValue(event.value, customVariables); - setVariable(key, value); + const key = getResolvedValue(event.key, customVariables, moduleId); + const value = getResolvedValue(event.value, customVariables, moduleId); + + console.log('here--- set-custom-variable', key, value, moduleId); + + setVariable(key, value, moduleId); return Promise.resolve(); // customAppVariables[key] = value; // const resp = useCurrentStateStore.getState().actions.setCurrentState({ @@ -687,20 +713,20 @@ export const createEventsSlice = (set, get) => ({ case 'get-custom-variable': { const { getVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - return getVariable(key); + const key = getResolvedValue(event.key, customVariables, moduleId); + return getVariable(key, moduleId); } case 'unset-all-custom-variables': { const { unsetAllVariables } = get(); - unsetAllVariables(); + unsetAllVariables(moduleId); return Promise.resolve(); } case 'unset-custom-variable': { const { unsetVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - unsetVariable(key); + const key = getResolvedValue(event.key, customVariables, moduleId); + unsetVariable(key, moduleId); return Promise.resolve(); // const customAppVariables = { ...getCurrentState().variables }; // delete customAppVariables[key]; @@ -717,9 +743,9 @@ export const createEventsSlice = (set, get) => ({ case 'set-page-variable': { const { setPageVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - const value = getResolvedValue(event.value, customVariables); - setPageVariable(key, value); + const key = getResolvedValue(event.key, customVariables, moduleId); + const value = getResolvedValue(event.value, customVariables, moduleId); + setPageVariable(key, value, moduleId); return Promise.resolve(); // const customPageVariables = { // ...getCurrentState().page.variables, @@ -749,20 +775,20 @@ export const createEventsSlice = (set, get) => ({ case 'get-page-variable': { const { getPageVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - return getPageVariable(key); + const key = getResolvedValue(event.key, customVariables, moduleId); + return getPageVariable(key, moduleId); } case 'unset-all-page-variables': { const { unsetAllPageVariables } = get(); - unsetAllPageVariables(); + unsetAllPageVariables(moduleId); return Promise.resolve(); } case 'unset-page-variable': { const { unsetPageVariable } = get(); - const key = getResolvedValue(event.key, customVariables); - unsetPageVariable(key); + const key = getResolvedValue(event.key, customVariables, moduleId); + unsetPageVariable(key, moduleId); return Promise.resolve(); // useStore.getState().unsetPageVariable(key); @@ -829,17 +855,12 @@ export const createEventsSlice = (set, get) => ({ // })); // console.log('actionArguments', event.componentSpecificActionParams); const actionArguments = event.componentSpecificActionParams.map((param) => { - const value = getResolvedValue(param.value, customVariables); + const value = getResolvedValue(param.value, customVariables, moduleId); return { ...param, value: value, - // value: resolveCode(re.valueWithBrackets, getAllExposedValues()), }; }); - // const actionArguments = _.map(event.componentSpecificActionParams, (param) => ({ - // ...param, - // value: resolveReferences(param.value, getAllExposedValues(), customVariables), - // })); const actionPromise = action && action(...actionArguments.map((argument) => argument.value)); return actionPromise ?? Promise.resolve(); @@ -858,7 +879,7 @@ export const createEventsSlice = (set, get) => ({ throw new Error('No page ID provided'); } const { switchPage } = get(); - const page = get().modules.canvas.pages.find((page) => page.id === event.pageId); + const page = get().modules[moduleId].pages.find((page) => page.id === event.pageId); const queryParams = event.queryParams || []; if (page.restricted && mode !== 'edit') { toast.error('Access to this page is restricted. Contact admin to know more.'); @@ -866,8 +887,8 @@ export const createEventsSlice = (set, get) => ({ const resolvedQueryParams = []; queryParams.forEach((param) => { resolvedQueryParams.push([ - getResolvedValue(param[0], customVariables), - getResolvedValue(param[1], customVariables), + getResolvedValue(param[0], customVariables, moduleId), + getResolvedValue(param[1], customVariables, moduleId), ]); }); const currentUrlParams = new URLSearchParams(window.location.search); @@ -880,7 +901,7 @@ export const createEventsSlice = (set, get) => ({ } } }); - switchPage(page.id, page.handle, resolvedQueryParams); + switchPage(page.id, page.handle, resolvedQueryParams, moduleId); } else { toast.error('Page is disabled'); //!TODO push to debugger @@ -904,14 +925,14 @@ export const createEventsSlice = (set, get) => ({ } }), - generateAppActions: (queryId, mode, isPreview = false) => { + generateAppActions: (queryId, mode, isPreview = false, moduleId = 'canvas') => { const { getCurrentPageComponents, dataQuery, eventsSlice, queryPanel, modules } = get(); const { previewQuery } = queryPanel; const { executeAction } = eventsSlice; - const currentComponents = Object.entries(getCurrentPageComponents()); + const currentComponents = Object.entries(getCurrentPageComponents(moduleId)); - const runQuery = (queryName = '', parameters) => { - const query = dataQuery.queries.modules['canvas'].find((query) => { + const runQuery = (queryName = '', parameters, moduleId = 'canvas') => { + const query = dataQuery.queries.modules[moduleId].find((query) => { const isFound = query.name === queryName; if (isPreview) { return isFound; @@ -944,7 +965,7 @@ export const createEventsSlice = (set, get) => ({ parameters: processedParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setVariable = (key = '', value = '') => { @@ -954,7 +975,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -964,7 +985,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'get-custom-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -981,7 +1002,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'unset-custom-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -991,14 +1012,14 @@ export const createEventsSlice = (set, get) => ({ alertType, message, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logout = () => { const event = { actionId: 'logout', }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const showModal = (modalName = '') => { @@ -1013,7 +1034,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'show-modal', modal, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const closeModal = (modalName = '') => { @@ -1028,7 +1049,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'close-modal', modal, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setLocalStorage = (key = '', value = '') => { @@ -1037,7 +1058,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const copyToClipboard = (contentToCopy = '') => { @@ -1045,7 +1066,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'copy-to-clipboard', contentToCopy, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const goToApp = (slug = '', queryParams = []) => { @@ -1054,7 +1075,7 @@ export const createEventsSlice = (set, get) => ({ slug, queryParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const generateFile = (fileName, fileType, data) => { @@ -1068,7 +1089,7 @@ export const createEventsSlice = (set, get) => ({ data, fileType, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setPageVariable = (key = '', value = '') => { @@ -1077,7 +1098,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const getPageVariable = (key = '') => { @@ -1085,7 +1106,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'get-page-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const unsetAllPageVariables = () => { @@ -1100,10 +1121,10 @@ export const createEventsSlice = (set, get) => ({ actionId: 'unset-page-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; - const switchPage = (pageHandle, queryParams = []) => { + const switchPage = (pageHandle, queryParams = [], moduleId = 'canvas') => { if (isPreview) { mode != 'view' && toast('Page will not be switched for query preview', { @@ -1111,7 +1132,7 @@ export const createEventsSlice = (set, get) => ({ }); return Promise.resolve(); } - const pages = modules.canvas.pages; + const pages = modules[moduleId].pages; const transformedPageHandle = pageHandle?.toLowerCase(); const pageId = pages.find((page) => page.handle === transformedPageHandle)?.id; @@ -1128,7 +1149,7 @@ export const createEventsSlice = (set, get) => ({ pageId, queryParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logInfo = (log, isFromTransformation) => { @@ -1144,7 +1165,7 @@ export const createEventsSlice = (set, get) => ({ eventType: 'customLog', query, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logError = (log, isFromTransformation = false) => { @@ -1160,7 +1181,7 @@ export const createEventsSlice = (set, get) => ({ eventType: 'customLog', query, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const log = (log, isFromTransformation = false) => { @@ -1176,7 +1197,7 @@ export const createEventsSlice = (set, get) => ({ eventType: 'customLog', query, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; return { diff --git a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js new file mode 100644 index 0000000000..c20768a257 --- /dev/null +++ b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js @@ -0,0 +1,139 @@ +const initialState = { + selectedNodes: new Set(), + searchedNodes: new Set(), + inspectorSearchValue: '', + inspectorSearchResults: new Set(), + selectedNodePath: null, +}; + +export const createInspectorSlice = (set, get) => ({ + ...initialState, + getSelectedNodes: () => { + const selectedNodes = get().selectedNodes; + return Array.from(selectedNodes); + }, + setSelectedNodes: (node) => { + const selectedNodes = get().selectedNodes; + const newSelectedNodes = new Set(selectedNodes); + if (newSelectedNodes.has(node)) { + newSelectedNodes.delete(node); + } else { + newSelectedNodes.add(node); + } + set({ selectedNodes: newSelectedNodes }); + }, + getInspectorSearchResults: () => { + const inspectorSearchResults = get().inspectorSearchResults; + return Array.from(inspectorSearchResults); + }, + setInspectorSearchValue: (value) => { + set({ inspectorSearchValue: value }); + }, + setInspectorSearchResults: (results) => { + set({ inspectorSearchResults: results }); + }, + setSelectedNodePath: (path) => { + set({ selectedNodePath: path }); + }, + getAllComponentChildrenById: (id) => { + const { getComponentDefinition, getResolvedComponent } = get(); + const component = getComponentDefinition(id); + const componentType = component?.component?.component; + switch (componentType) { + case 'Container': + case 'Form': + case 'ModalV2': + return [ + ...get().getContainerChildrenMapping(id), + ...get().getContainerChildrenMapping(`${id}-header`), + ...get().getContainerChildrenMapping(`${id}-footer`), + ]; + case 'Tabs': { + const tabs = getResolvedComponent(id)?.properties?.tabs; + const children = Array.isArray(tabs) ? tabs : []; + const res = children + ?.map((tab) => { + const tabId = `${id}-${tab.id}`; + return get().getContainerChildrenMapping(tabId); + }) + .reduce((acc, curr) => { + return [...acc, ...curr]; + }, []); + return res; + } + default: + return get().getContainerChildrenMapping(id); + } + }, + + formatInspectorComponentData: ( + componentIdNameMapping, + exposedComponentsVariables, + searchablePaths = new Set(), + moduleId = 'canvas' + ) => { + const { getComponentDefinition, getAllComponentChildrenById } = get(); + const data = Object.entries(componentIdNameMapping) + .filter(([key]) => { + const component = getComponentDefinition(key, moduleId); + return !component?.component?.parent; + }) + .map(([key, name]) => { + const component = getComponentDefinition(key, moduleId); + let parentComponentType = null; + if (component?.component?.parent) { + const parentComponent = getComponentDefinition(component.component.parent, moduleId); + parentComponentType = parentComponent?.component?.component; + } + return { + key, + name: name || key, + parentType: parentComponentType, + }; + }) + .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + + const reduceData = (obj, path = 'components', level = 1) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + + return data + .filter((item) => item.name) + .reduce((acc, { key, name, parentType }) => { + const currentPath = `components.${name}`; + const actualPath = `${path}.${name}`; + searchablePaths.add(actualPath); + const children = getAllComponentChildrenById(key).map((childKey) => { + const childComponent = getComponentDefinition(childKey); + let parentComponentType = null; + if (childComponent?.component?.parent) { + const parentComponent = getComponentDefinition(childComponent.component.parent); + parentComponentType = parentComponent?.component?.component; + } + return { + key: childKey, + name: childComponent?.component?.name, + parentType: parentComponentType, + }; + }); + + return [ + ...acc, + { + id: actualPath, + name, + children: reduceData(children, actualPath, level + 1), + metadata: { + type: 'components', + path: currentPath, + parentType: parentType, + actualPath, + }, + }, + ]; + }, []); + }; + + return reduceData(data); + }, +}); diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index 367ca4cf0c..8a3989a05b 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -30,16 +30,23 @@ export const createLeftSideBarSlice = (set, get) => ({ ), setPathToBeInspected: (pathToBeInspected) => set(() => ({ pathToBeInspected }), false, 'setPathToBeInspected'), setComponentToInspect: (componentToInspect) => { - const { setPathToBeInspected, setSelectedSidebarItem, toggleLeftSidebar, selectedSidebarItem } = get(); - setPathToBeInspected(['components', componentToInspect]); + const { + setPathToBeInspected, + setSelectedSidebarItem, + toggleLeftSidebar, + selectedSidebarItem, + setSelectedNodePath, + } = get(); + // setPathToBeInspected(['components', componentToInspect]); + setSelectedNodePath(`components.${componentToInspect}`); if (selectedSidebarItem !== 'inspect') { setSelectedSidebarItem('inspect'); toggleLeftSidebar(true); } }, - getComponentIdToAutoScroll: (componentId) => { + getComponentIdToAutoScroll: (componentId, moduleId = 'canvas') => { const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get(); - const currentPageComponents = getCurrentPageComponents(); + const currentPageComponents = getCurrentPageComponents(moduleId); let targetComponentId = componentId; let current = componentId; @@ -66,7 +73,7 @@ export const createLeftSideBarSlice = (set, get) => ({ const tabId = parentId.replace(regForTabs, ''); // Extract tab id from parent id - const { currentTab } = getAllExposedValues().components?.[tabId] || {}; + const { currentTab } = getAllExposedValues(moduleId).components?.[tabId] || {}; const activeTabIndex = Number(currentTab); nextPossibleCandidate = tabId; diff --git a/frontend/src/AppBuilder/_stores/slices/loaderSlice.js b/frontend/src/AppBuilder/_stores/slices/loaderSlice.js index 6b1da506e1..6a0ef71271 100644 --- a/frontend/src/AppBuilder/_stores/slices/loaderSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/loaderSlice.js @@ -1,10 +1,43 @@ const initialState = { - isEditorLoading: true, - isCanvasLoading: false, + loaderStore: { + modules: { + canvas: { + isEditorLoading: true, + }, + }, + }, }; -export const createLoaderSlice = (set) => ({ +export const createLoaderSlice = (set, get) => ({ ...initialState, - setEditorLoading: (status) => set(() => ({ isEditorLoading: status }), false, 'setEditorLoading'), - setCanvasLoading: (status) => set(() => ({ isCanvasLoading: status }), false, 'setCanvasLoading'), + initializeLoaderSlice: (moduleId) => { + set( + (state) => { + state.loaderStore.modules[moduleId] = { + ...initialState.loaderStore.modules.canvas, + }; + }, + false, + 'initializeLoaderSlice' + ); + }, + setEditorLoading: (status, moduleId = 'canvas') => + set( + (state) => { + state.loaderStore.modules[moduleId].isEditorLoading = status; + }, + false, + 'setEditorLoading' + ), + setIsLoaderLoading: (status, moduleId = 'canvas') => + set( + (state) => { + state.loaderStore.modules[moduleId] = { + isLoaderLoading: status, + }; + }, + false, + 'setIsLoaderLoading' + ), + getEditorLoading: (moduleId) => get().loaderStore.modules[moduleId].isEditorLoading, }); diff --git a/frontend/src/AppBuilder/_stores/slices/modeSlice.js b/frontend/src/AppBuilder/_stores/slices/modeSlice.js index a24d5c93d6..ce2672a91a 100644 --- a/frontend/src/AppBuilder/_stores/slices/modeSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/modeSlice.js @@ -1,8 +1,33 @@ const initialState = { - currentMode: 'view', + modeStore: { + modules: { + canvas: { + currentMode: 'view', + }, + }, + }, }; -export const createModeSlice = (set) => ({ +export const createModeSlice = (set, get) => ({ ...initialState, - setCurrentMode: (currentMode) => set(() => ({ currentMode }), false, 'setCurrentMode'), + initializeModeSlice: (moduleId) => { + set( + (state) => { + state.modeStore.modules[moduleId] = { + ...initialState.modeStore.modules.canvas, + }; + }, + false, + 'initializeModeSlice' + ); + }, + setCurrentMode: (currentMode, moduleId = 'canvas') => + set( + (state) => { + state.modeStore.modules[moduleId].currentMode = currentMode; + }, + false, + 'setCurrentMode' + ), + getCurrentMode: (moduleId) => get().modeStore.modules[moduleId].currentMode, }); diff --git a/frontend/src/AppBuilder/_stores/slices/moduleSlice.js b/frontend/src/AppBuilder/_stores/slices/moduleSlice.js new file mode 100644 index 0000000000..5c97f74cf1 --- /dev/null +++ b/frontend/src/AppBuilder/_stores/slices/moduleSlice.js @@ -0,0 +1,5 @@ +import { getEditionSpecificSlice } from '../../../modules/common/helpers/getEditionSpecificSlice'; + +const createModuleSlice = getEditionSpecificSlice('createModuleSlice'); + +export { createModuleSlice }; diff --git a/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js b/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js index f025563f66..8043a0bd18 100644 --- a/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js @@ -21,14 +21,14 @@ export const createMultiplayerSlice = (set, get) => ({ diff, type, operation, - pageId: get().currentPageId, + pageId: get().getCurrentPageId('canvas'), versionId: get().selectedVersion?.id, }); } }, processUpdate: ({ diff, type, operation, pageId, versionId }) => { - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); const currentVersionId = get().selectedVersion?.id; if (currentPageId === pageId && currentVersionId === versionId) diff --git a/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js b/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js index 1ff607d873..8c67ab9825 100644 --- a/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js @@ -55,7 +55,8 @@ const createPageUpdateCommand = } }); - const { app, currentVersionId } = get(); + const { appStore, currentVersionId } = get(); + const app = appStore.modules.canvas.app; const diff = _.zipObject(updatePaths, values); if (enableSave) savePageChanges(app.appId, currentVersionId, pageId, diff); }; @@ -195,10 +196,8 @@ export const createPageMenuSlice = (set, get) => { updatePageWithPermissions: (pageId, value) => updatePageWithPermissions(pageId, [value])(set, get), // unsure about this one clonePage: async (pageId) => { - const { - app: { appId }, - currentVersionId, - } = get(); + const { getAppId, currentVersionId } = get(); + const appId = getAppId('canvas'); const pages = get().modules.canvas.pages; const data = await appVersionService.clonePage(appId, currentVersionId, pageId); const newPages = data?.pages; @@ -216,19 +215,21 @@ export const createPageMenuSlice = (set, get) => { } }, deletePage: async (pageId) => { - const { app, currentVersionId } = get(); + const { getAppId, getHomePageId, currentVersionId } = get(); + const appId = getAppId('canvas'); + const homePageId = getHomePageId('canvas'); const diff = { pageId: pageId, }; const pages = get().modules.canvas.pages; - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); const switchPage = get().switchPage; if (pages.length === 1) { toast.error('You cannot delete the only page in your app.'); return; } if (currentPageId === pageId) { - const homePage = pages.find((p) => p.id === app.homePageId); + const homePage = pages.find((p) => p.id === homePageId); switchPage(homePage.id, homePage.handle); } set((state) => { @@ -237,7 +238,7 @@ export const createPageMenuSlice = (set, get) => { state.showEditingPopover = false; state.editingPage = null; }); - await savePageChanges(app.appId, currentVersionId, pageId, diff, 'delete'); + await savePageChanges(appId, currentVersionId, pageId, diff, 'delete'); toast.success('Page deleted successfully'); }, /* @@ -246,11 +247,11 @@ export const createPageMenuSlice = (set, get) => { * If home page is in the group, the group cannot be deleted * If current page is in the group, the page will be switched to home page */ - deletePageGroup: async (pageGroupId, deleteAssociatedPages = false) => { - const { app, currentVersionId } = get(); + deletePageGroup: async (pageGroupId, deleteAssociatedPages = false, moduleId = 'canvas') => { + const { getAppId, getHomePageId, currentVersionId } = get(); + const appId = getAppId(moduleId); + const homePageId = getHomePageId(moduleId); const pages = get().modules.canvas.pages; - - const homePageId = get().app.homePageId; const diff = { pageId: pageGroupId, deleteAssociatedPages, @@ -263,7 +264,7 @@ export const createPageMenuSlice = (set, get) => { if (pages[i].id === homePageId && pages[i].pageGroupId === pageGroupId) { isHomePageInGroup = true; } - if (pages[i].id === get().currentPageId && pages[i].pageGroupId === pageGroupId) { + if (pages[i].id === get().getCurrentPageId('canvas') && pages[i].pageGroupId === pageGroupId) { isCurrentPageInGroup = true; } } @@ -280,10 +281,10 @@ export const createPageMenuSlice = (set, get) => { }); // switch page to home page if current page is in the group if (isCurrentPageInGroup) { - const homePage = pages.find((p) => p.id === app.homePageId); + const homePage = pages.find((p) => p.id === homePageId); get().switchPage(homePage.id, homePage.handle); } - await savePageChanges(app.appId, currentVersionId, pageGroupId, diff, 'delete'); + await savePageChanges(appId, currentVersionId, pageGroupId, diff, 'delete'); } else { set((state) => { const pages = get().modules.canvas.pages; @@ -302,25 +303,26 @@ export const createPageMenuSlice = (set, get) => { state.modules.canvas.pages = newPages; state.showDeleteConfirmationModal = false; }); - await savePageChanges(app.appId, currentVersionId, pageGroupId, diff, 'delete'); + await savePageChanges(appId, currentVersionId, pageGroupId, diff, 'delete'); } }, - markAsHomePage: async (pageId) => { - const { app, currentVersionId, editingPage } = get(); + markAsHomePage: async (pageId, moduleId = 'canvas') => { + const { getAppId, currentVersionId, editingPage } = get(); + const appId = getAppId(moduleId); const diff = { homePageId: pageId, }; set((state) => { - state.app.homePageId = pageId; + state.appStore.modules[moduleId].app.homePageId = pageId; state.showEditingPopover = false; state.editingPage = null; }); - await savePageChanges(app.appId, currentVersionId, editingPage.id, diff, 'update', null); + await savePageChanges(appId, currentVersionId, editingPage.id, diff, 'update', null); }, reorderPages: async (reorderdPages) => { const diff = {}; - const currentPageId = get().currentPageId; + const currentPageId = get().getCurrentPageId('canvas'); // update index of everything to avoid inconsistencies reorderdPages.forEach((page, index) => { diff[page.id] = { @@ -332,8 +334,9 @@ export const createPageMenuSlice = (set, get) => { set((state) => { state.modules.canvas.pages = reorderdPages; }); - const { app, currentVersionId } = get(); - await savePageChanges(app.appId, currentVersionId, currentPageId, diff, 'update', 'pages/reorder'); + const { getAppId, currentVersionId } = get(); + const appId = getAppId('canvas'); + await savePageChanges(appId, currentVersionId, currentPageId, diff, 'update', 'pages/reorder'); }, addNewPage: async (name, handle, isPageGroup = false) => { @@ -375,8 +378,9 @@ export const createPageMenuSlice = (set, get) => { set((state) => { state.modules.canvas.pages.push(pageObject); }); - const { app, currentVersionId } = get(); - await savePageChanges(app.appId, currentVersionId, '', pageObject, 'create', 'pages'); + const { getAppId, currentVersionId } = get(); + const appId = getAppId('canvas'); + await savePageChanges(appId, currentVersionId, '', pageObject, 'create', 'pages'); if (!isPageGroup) get().switchPage(newPageId, newHandle); }, @@ -404,10 +408,11 @@ export const createPageMenuSlice = (set, get) => { newOptions[key] = hexCode; } } - const { app, currentVersionId, currentPageId } = get(); + const { getAppId, currentVersionId, currentPageId } = get(); + const appId = getAppId('canvas'); try { const res = await appVersionService.autoSaveApp( - app.appId, + appId, currentVersionId, { pageSettings: { [type]: newOptions } }, 'page_settings', diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 06774289ba..903b548ca2 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -72,13 +72,13 @@ export const createQueryPanelSlice = (set, get) => ({ 'setQueryPanelHeight' ); }, // updateQueryPanelHeight - setSelectedQuery: (queryId) => { + setSelectedQuery: (queryId, moduleId = 'canvas') => { set((state) => { if (queryId === null) { state.queryPanel.selectedQuery = null; return; } - const query = get().dataQuery.queries.modules.canvas.find((query) => query.id === queryId); + const query = get().dataQuery.queries.modules[moduleId].find((query) => query.id === queryId); state.queryPanel.selectedQuery = query; return; }); @@ -168,7 +168,7 @@ export const createQueryPanelSlice = (set, get) => ({ 'setLoadingDataQueries' ), - onQueryConfirmOrCancel: (queryConfirmationData, isConfirm = false, mode = 'edit') => { + onQueryConfirmOrCancel: (queryConfirmationData, isConfirm = false, mode = 'edit', moduleId = 'canvas') => { const { queryPanel, dataQuery, setResolvedQuery } = get(); const { runQuery } = queryPanel; const { queryConfirmationList } = dataQuery; @@ -191,13 +191,21 @@ export const createQueryPanelSlice = (set, get) => ({ true, mode, queryConfirmationData.parameters, - queryConfirmationData.shouldSetPreviewData + undefined, + undefined, + queryConfirmationData.shouldSetPreviewData, + false, + moduleId ); !isConfirm && - setResolvedQuery(queryConfirmationData.queryId, { - isLoading: false, - }); + setResolvedQuery( + queryConfirmationData.queryId, + { + isLoading: false, + }, + moduleId + ); }, runQuery: ( @@ -217,7 +225,7 @@ export const createQueryPanelSlice = (set, get) => ({ dataQuery: dataQuerySlice, queryPanel, setResolvedQuery, - app, + appStore, selectedEnvironment, isPublicAccess, currentVersionId, @@ -272,14 +280,18 @@ export const createQueryPanelSlice = (set, get) => ({ let dataQuery = {}; //for viewer we will only get the environment id from the url - const { currentAppEnvironmentId, environmentId } = app; + const { currentAppEnvironmentId, environmentId } = appStore.modules[moduleId].app; if (shouldSetPreviewData) { setPreviewPanelExpanded(true); setPreviewLoading(true); - setResolvedQuery(queryId, { - isLoading: true, - }); + setResolvedQuery( + queryId, + { + isLoading: true, + }, + moduleId + ); queryPreviewData && setPreviewData(''); } @@ -300,10 +312,11 @@ export const createQueryPanelSlice = (set, get) => ({ } // const queryState = { ...getCurrentState(), parameters }; - const queryState = { ...get().getAllExposedValues('canvas'), parameters }; + const queryState = { ...get().getAllExposedValues(moduleId), parameters }; + const options = getQueryVariables(dataQuery.options, queryState, { - components: get().getComponentNameIdMapping(), - queries: get().getQueryNameIdMapping(), + components: get().getComponentNameIdMapping(moduleId), + queries: get().getQueryNameIdMapping(moduleId), }); if (dataQuery.options?.requestConfirmation) { const queryConfirmation = { @@ -333,18 +346,22 @@ export const createQueryPanelSlice = (set, get) => ({ queryPreviewData && setPreviewData(''); } - setResolvedQuery(queryId, { - isLoading: true, - data: [], - rawData: [], - id: queryId, - }); + setResolvedQuery( + queryId, + { + isLoading: true, + data: [], + rawData: [], + id: queryId, + }, + moduleId + ); let queryExecutionPromise = null; if (query.kind === 'runjs') { - queryExecutionPromise = executeMultilineJS(query.options?.code, query?.id, false, mode, parameters); + queryExecutionPromise = executeMultilineJS(query.options?.code, query?.id, false, mode, parameters, moduleId); } else if (query.kind === 'runpy') { - queryExecutionPromise = executeRunPycode(query.options?.code, query, false, mode, queryState); + queryExecutionPromise = executeRunPycode(query.options?.code, query, false, mode, queryState, moduleId); } else if (query.kind === 'workflows') { queryExecutionPromise = executeWorkflow( moduleId, @@ -355,11 +372,16 @@ export const createQueryPanelSlice = (set, get) => ({ (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id //TODO: currentAppEnvironmentId may no longer required. Need to check ); } else { + let versionId = currentVersionId; + // IMPORTANT: This logic needs to be changed when we implement the module versioning + if (moduleId !== 'canvas') { + versionId = get().resolvedStore.modules.canvas.components[moduleId].properties.moduleVersionId; + } queryExecutionPromise = dataqueryService.run( queryId, options, query?.options, - currentVersionId, + versionId, !isPublicAccess ? (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id : undefined, //TODO: currentAppEnvironmentId may no longer required. Need to check currentMode ); @@ -430,17 +452,21 @@ export const createQueryPanelSlice = (set, get) => ({ isQuerySuccessLog: false, }); - setResolvedQuery(queryId, { - isLoading: false, - ...(query.kind === 'restapi' || data.data.type === 'tj-401' - ? { - metadata: data.metadata, - request: data.data.requestObject, - response: data.data.responseObject, - responseHeaders: data.data.responseHeaders, - } - : {}), - }); + setResolvedQuery( + queryId, + { + isLoading: false, + ...(query.kind === 'restapi' || data.data.type === 'tj-401' + ? { + metadata: data.metadata, + request: data.data.requestObject, + response: data.data.responseObject, + responseHeaders: data.data.responseHeaders, + } + : {}), + }, + moduleId + ); resolve(data); onEvent('onDataQueryFailure', queryEvents); @@ -454,12 +480,17 @@ export const createQueryPanelSlice = (set, get) => ({ query.options.transformation, query.options.transformationLanguage, query, - 'edit' + 'edit', + moduleId ); - if (finalData?.status === 'failed') { - setResolvedQuery(queryId, { - isLoading: false, - }); + if (finalData.status === 'failed') { + setResolvedQuery( + queryId, + { + isLoading: false, + }, + moduleId + ); resolve(finalData); onEvent('onDataQueryFailure', queryEvents); @@ -491,14 +522,18 @@ export const createQueryPanelSlice = (set, get) => ({ errorTarget: 'Queries', }); - setResolvedQuery(queryId, { - isLoading: false, - data: finalData, - rawData, - metadata: data?.metadata, - request: data?.metadata?.request, - response: data?.metadata?.response, - }); + setResolvedQuery( + queryId, + { + isLoading: false, + data: finalData, + rawData, + metadata: data?.metadata, + request: data?.metadata?.request, + response: data?.metadata?.response, + }, + moduleId + ); resolve({ status: 'ok', data: finalData }); onEvent('onDataQuerySuccess', queryEvents, mode); @@ -513,7 +548,7 @@ export const createQueryPanelSlice = (set, get) => ({ }, previewQuery: (query, calledFromQuery = false, userSuppliedParameters = {}, moduleId = 'canvas') => { - const { eventsSlice, queryPanel, app, currentVersionId, selectedEnvironment } = get(); + const { eventsSlice, queryPanel, appStore, currentVersionId, selectedEnvironment } = get(); const { queryPreviewData, setPreviewLoading, @@ -537,6 +572,8 @@ export const createQueryPanelSlice = (set, get) => ({ let parameters = userSuppliedParameters; + const app = appStore.modules[moduleId].app; + // passing current env through props only for querymanager const { environmentId } = app; const currentAppEnvironmentId = selectedEnvironment?.id || ''; @@ -566,7 +603,7 @@ export const createQueryPanelSlice = (set, get) => ({ } // const queryState = { ...getCurrentState(), parameters }; - const queryState = { ...get().getAllExposedValues(), parameters }; + const queryState = { ...get().getAllExposedValues(moduleId), parameters }; const options = getQueryVariables(query.options, queryState, { components: get().getComponentNameIdMapping(), queries: get().getQueryNameIdMapping(), @@ -658,7 +695,8 @@ export const createQueryPanelSlice = (set, get) => ({ query.options.transformation, query.options.transformationLanguage, query, - 'edit' + 'edit', + moduleId ); if (finalData?.status === 'failed') { onEvent('onDataQueryFailure', queryEvents); @@ -740,32 +778,32 @@ export const createQueryPanelSlice = (set, get) => ({ let result = {}; try { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); const queriesInCurentState = deepClone(resolvedState.queries); const appStateVars = deepClone(resolvedState.variables) ?? {}; if (!isEmpty(query)) { - const actions = generateAppActions(query.id, mode, isPreview); + const actions = generateAppActions(query.id, mode, isPreview, moduleId); for (const key of Object.keys(queriesInCurentState)) { queriesInCurentState[key] = { ...queriesInCurentState[key], run: () => { const query = dataQuery.queries.modules?.[moduleId].find((q) => q.name === key); - return actions.runQuery(query.name); + return actions.runQuery(query.name, undefined, moduleId); }, getData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].data; }, getRawData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].rawData; }, getloadingState: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].isLoading; }, }; @@ -807,14 +845,21 @@ export const createQueryPanelSlice = (set, get) => ({ return pyodide.isPyProxy(result) ? convertMapSet(result.toJs()) : result; }, - runTransformation: async (rawData, transformation, transformationLanguage = 'javascript', query, mode = 'edit') => { + runTransformation: async ( + rawData, + transformation, + transformationLanguage = 'javascript', + query, + mode = 'edit', + moduleId = 'canvas' + ) => { const data = rawData; const { queryPanel: { runPythonTransformation, createProxy }, getResolvedState, } = get(); let result = {}; - const currentState = getResolvedState(); + const currentState = getResolvedState(moduleId); if (transformationLanguage === 'python') { result = await runPythonTransformation(currentState, data, transformation, query, mode); @@ -934,12 +979,10 @@ export const createQueryPanelSlice = (set, get) => ({ // queries: updatedQueries, // }); }, - executeWorkflow: async (moduleId, query, workflowId, _blocking = false, params = {}, appEnvId) => { - const { - app: { appId }, - getAllExposedValues, - } = get(); - const currentState = getAllExposedValues(); + executeWorkflow: async (moduleId = 'canvas', query, workflowId, _blocking = false, params = {}, appEnvId) => { + const { getAppId, getAllExposedValues } = get(); + const appId = getAppId('canvas'); + const currentState = getAllExposedValues(moduleId); const resolvedParams = get().resolveReferences(moduleId, params, currentState, {}, {}); if (query.restricted) { @@ -999,7 +1042,7 @@ export const createQueryPanelSlice = (set, get) => ({ return isValidCode; } - const currentState = getAllExposedValues(); + // const currentState = getAllExposedValues(); let result = {}, error = null; @@ -1009,7 +1052,7 @@ export const createQueryPanelSlice = (set, get) => ({ parameters = {}; } - const actions = generateAppActions(queryId, mode, isPreview); + const actions = generateAppActions(queryId, mode, isPreview, moduleId); const queryDetails = dataQuery.queries.modules?.[moduleId].find((q) => q.id === queryId); @@ -1053,7 +1096,7 @@ export const createQueryPanelSlice = (set, get) => ({ //this will handle the preview case where you cannot find the queryDetails in state. formattedParams = { ...parameters }; } - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); const queriesInResolvedState = deepClone(resolvedState.queries); for (const key of Object.keys(resolvedState.queries)) { queriesInResolvedState[key] = { @@ -1065,21 +1108,21 @@ export const createQueryPanelSlice = (set, get) => ({ const processedParams = {}; const query = dataQuery.queries.modules?.[moduleId].find((q) => q.name === key); query.options.parameters?.forEach((arg) => (processedParams[arg.name] = params[arg.name])); - return actions.runQuery(query.name, processedParams); + return actions.runQuery(query.name, processedParams, moduleId); }, getData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].data; }, getRawData: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].rawData; }, getloadingState: () => { - const resolvedState = get().getResolvedState(); + const resolvedState = get().getResolvedState(moduleId); return resolvedState.queries[key].isLoading; }, }; diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index a253346eec..3b6877b726 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -3,15 +3,6 @@ import { resolveDynamicValues } from '../utils'; import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast'; import { componentTypeDefinitionMap } from '@/AppBuilder/WidgetManager'; import _ from 'lodash'; -import { - reservedKeyword, - resolveString, - removeNestedDoubleCurlyBraces, - getDynamicVariables, - resolveCode, -} from '@/_helpers/utils'; - -import { validateMultilineCode } from '@/_helpers/utility'; const initialState = { resolvedStore: { @@ -25,7 +16,7 @@ const initialState = { secrets: {}, customResolvables: {}, exposedValues: { - queries: {}, + queries: {} /* IMPORTANT: Query is subscribed by the moduleContainer component */, components: {}, variables: {}, constants: {}, @@ -50,6 +41,17 @@ export const DEFAULT_COMPONENT_STRUCTURE = { export const createResolvedSlice = (set, get) => ({ ...initialState, + initializeResolvedSlice: (moduleId) => { + set( + (state) => { + state.resolvedStore.modules[moduleId] = { + ...initialState.resolvedStore.modules.canvas, + }; + }, + false, + 'initializeResolvedSlice' + ); + }, setResolvedGlobals: (objKey, values, moduleId = 'canvas') => { set( (state) => { @@ -71,7 +73,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedGlobals' ); Object.entries(values).forEach(() => { - get().updateDependencyValues(`globals.${objKey}`); + get().updateDependencyValues(`globals.${objKey}`, moduleId); }); }, setResolvedConstants: (constants = {}, moduleId = 'canvas') => { @@ -85,7 +87,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedConstants' ); Object.entries(constants).forEach(([key, value]) => { - get().updateDependencyValues(`constants.${key}`); + get().updateDependencyValues(`constants.${key}`, moduleId); }); }, @@ -108,7 +110,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedPageConstants' ); Object.entries(constants).forEach(([key, value]) => { - get().updateDependencyValues(`page.${key}`); + get().updateDependencyValues(`page.${key}`, moduleId); }); }, @@ -121,7 +123,7 @@ export const createResolvedSlice = (set, get) => ({ false, 'setVariables' ); - get().updateDependencyValues(`variables.${key}`); + get().updateDependencyValues(`variables.${key}`, moduleId); get().checkAndSetTrueBuildSuggestionsFlag(); }, @@ -137,8 +139,8 @@ export const createResolvedSlice = (set, get) => ({ false, 'unsetVariable' ); - get().removeNode(`variables.${key}`); - get().updateDependencyValues(`variables.${key}`); + get().removeNode(`variables.${key}`, moduleId); + get().updateDependencyValues(`variables.${key}`, moduleId); }, unsetAllVariables: (moduleId = 'canvas') => { @@ -165,7 +167,7 @@ export const createResolvedSlice = (set, get) => ({ false, 'setPageVariable' ); - get().updateDependencyValues(`page.variables.${key}`); + get().updateDependencyValues(`page.variables.${key}`, moduleId); get().checkAndSetTrueBuildSuggestionsFlag(); }, @@ -180,8 +182,8 @@ export const createResolvedSlice = (set, get) => ({ false, 'unsetPageVariable' ); - get().removeNode(`page.variables.${key}`); - get().updateDependencyValues(`page.variables.${key}`); + get().removeNode(`page.variables.${key}`, moduleId); + get().updateDependencyValues(`page.variables.${key}`, moduleId); }, unsetAllPageVariables: (moduleId = 'canvas') => { @@ -213,13 +215,13 @@ export const createResolvedSlice = (set, get) => ({ Object.entries(details).forEach(([key, value]) => { if (['isLoading', 'data', 'rawData', 'request', 'response', 'responseHeaders', 'metadata'].includes(key)) { - if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`); + if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`, moduleId); } }); // Flag to update the codehinter suggestions get().checkAndSetTrueBuildSuggestionsFlag(); }, - initialiseResolvedQuery(querIds, moduleId = 'canvas') { + initialiseResolvedQuery: (querIds, moduleId = 'canvas') => { const defaultObject = {}; querIds.forEach((queryId) => { defaultObject[queryId] = { @@ -248,7 +250,7 @@ export const createResolvedSlice = (set, get) => ({ setResolvedComponents: (components, moduleId = 'canvas') => { const validateComponents = get().debugger.validateComponents; - const validatedComponents = validateComponents(components); + const validatedComponents = validateComponents(components, moduleId); set( (state) => { @@ -277,7 +279,7 @@ export const createResolvedSlice = (set, get) => ({ } */ setResolvedComponentByProperty: (componentId, type, property, value, index = null, moduleId = 'canvas') => { - value = get().debugger.validateProperty(componentId, type, property, value); + value = get().debugger.validateProperty(componentId, type, property, value, moduleId); set( (state) => { @@ -338,7 +340,7 @@ export const createResolvedSlice = (set, get) => ({ payload: { componentId, property, value, moduleId }, } ); - get().updateDependencyValues(`components.${componentId}.${property}`); + get().updateDependencyValues(`components.${componentId}.${property}`, moduleId); }, setExposedValues: (id, type, values, moduleId = 'canvas') => { @@ -359,7 +361,7 @@ export const createResolvedSlice = (set, get) => ({ } ); Object.entries(values).forEach(([key, value]) => { - if (typeof value !== 'function') get().updateDependencyValues(`components.${id}.${key}`); + if (typeof value !== 'function') get().updateDependencyValues(`components.${id}.${key}`, moduleId); }); }, @@ -368,7 +370,7 @@ export const createResolvedSlice = (set, get) => ({ if (val && Object.keys(val).length > 0) return; const component = componentTypeDefinitionMap[componentType]; if (!component) return; - const parentComponentType = get().getComponentDefinition(parentId)?.component?.component; + const parentComponentType = get().getComponentDefinition(parentId, moduleId)?.component?.component; if (['Form', 'Listview'].includes(parentComponentType)) return; const exposedVariables = component.exposedVariables || {}; get().setExposedValues(id, 'components', exposedVariables, moduleId); @@ -424,7 +426,7 @@ export const createResolvedSlice = (set, get) => ({ }, getExposedValueOfComponent: (componentId, moduleId = 'canvas') => { try { - const components = get().getCurrentPageComponents(); + const components = get().getCurrentPageComponents(moduleId); const { component: { parent: parentId, name: componentName }, } = components[componentId]; @@ -501,6 +503,9 @@ export const createResolvedSlice = (set, get) => ({ state.resolvedStore.modules[moduleId].exposedValues.components = {}; state.resolvedStore.modules[moduleId].exposedValues.variables = {}; state.resolvedStore.modules[moduleId].exposedValues.globals = {}; + if (state.resolvedStore.modules[moduleId].exposedValues.input) { + state.resolvedStore.modules[moduleId].exposedValues.input = {}; + } if (state.resolvedStore.modules[moduleId].exposedValues.page?.variables) { state.resolvedStore.modules[moduleId].exposedValues.page.variables = {}; } @@ -530,7 +535,7 @@ export const createResolvedSlice = (set, get) => ({ }, // this function simply replaces the id with name for queries and components inside resolvedStore - getResolvedState: (key, moduleId = 'canvas') => { + getResolvedState: (moduleId = 'canvas', key) => { const state = { components: {}, queries: {}, @@ -574,7 +579,7 @@ export const createResolvedSlice = (set, get) => ({ const objectType = typeof object; let error; - const state = _state ?? get().getAllExposedValues(); + const state = _state ?? get().getAllExposedValues(moduleId); if (_state?.parameters) { state.parameters = { ..._state.parameters }; @@ -613,4 +618,41 @@ export const createResolvedSlice = (set, get) => ({ } } }, + + setModuleInputs: (key, value, moduleId = 'canvas') => { + set( + (state) => { + if (!state.resolvedStore.modules[moduleId].exposedValues.input) { + state.resolvedStore.modules[moduleId].exposedValues.input = {}; + } + state.resolvedStore.modules[moduleId].exposedValues.input[key] = value; + }, + false, + 'setModuleInputs' + ); + get().updateDependencyValues(`input.${key}`, moduleId); + }, + setModuleOutputs: (key, value, moduleId = 'canvas') => { + set( + (state) => { + if (!state.resolvedStore.modules[moduleId].exposedValues.output) { + state.resolvedStore.modules[moduleId].exposedValues.output = {}; + } + state.resolvedStore.modules[moduleId].exposedValues.output[key] = value; + }, + false, + 'setModuleOutputs' + ); + get().updateDependencyValues(`output.${key}`, moduleId); + }, + clearModuleInputs: (moduleId = 'canvas') => { + set((state) => { + state.resolvedStore.modules[moduleId].exposedValues.input = {}; + }); + }, + clearModuleOutputs: (moduleId = 'canvas') => { + set((state) => { + state.resolvedStore.modules[moduleId].exposedValues.output = {}; + }); + }, }); diff --git a/frontend/src/AppBuilder/_stores/store.js b/frontend/src/AppBuilder/_stores/store.js index 4d1392fc7c..f7b83e9704 100644 --- a/frontend/src/AppBuilder/_stores/store.js +++ b/frontend/src/AppBuilder/_stores/store.js @@ -28,6 +28,8 @@ import { createDebuggerSlice } from './slices/debuggerSlice'; import { createGitSyncSlice } from './slices/gitSyncSlice'; import { createAiSlice } from './slices/aiSlice'; import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice'; +import { createInspectorSlice } from './slices/inspectorSlice'; +import { createModuleSlice } from './slices/moduleSlice'; export default create( zustandDevTools( @@ -60,6 +62,8 @@ export default create( ...createGitSyncSlice(...state), ...createAiSlice(...state), ...createWhiteLabellingSlice(...state), + ...createInspectorSlice(...state), + ...createModuleSlice(...state), })), { name: 'App Builder Store', anonymousActionType: 'unknown' } ) diff --git a/frontend/src/AppBuilder/_stores/utils.js b/frontend/src/AppBuilder/_stores/utils.js index 012d59aaf5..f333654a16 100644 --- a/frontend/src/AppBuilder/_stores/utils.js +++ b/frontend/src/AppBuilder/_stores/utils.js @@ -6,7 +6,7 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { dfs } from '@/_stores/handleReferenceTransactions'; import { extractAndReplaceReferencesFromString as extractAndReplaceReferencesFromStringAst } from '@/AppBuilder/_stores/ast'; -import _ from 'lodash'; +var _ = require('lodash'); const resetters = []; @@ -148,6 +148,7 @@ export const resolveCode = ( 'queries', 'globals', 'page', + 'input', 'client', 'server', 'constants', @@ -165,6 +166,7 @@ export const resolveCode = ( isJsCode ? state?.queries : undefined, isJsCode ? state?.globals : undefined, isJsCode ? state?.page : undefined, + isJsCode ? state?.input : undefined, isJsCode ? undefined : state?.client, isJsCode ? undefined : state?.server, state?.constants, // Passing constants as an argument allows the evaluated code to access and utilize the constants value correctly. diff --git a/frontend/src/AppLoader/AppLoader.jsx b/frontend/src/AppLoader/AppLoader.jsx index dba9883d14..e276360843 100644 --- a/frontend/src/AppLoader/AppLoader.jsx +++ b/frontend/src/AppLoader/AppLoader.jsx @@ -12,8 +12,14 @@ const AppLoader = (props) => { resetAllStores(); }, []); - if (appType === 'front-end') return ; - else if (appType === 'workflow') return ; + switch (appType) { + case 'front-end': + return ; + case 'workflow': + return ; + case 'module': + return ; + } }; export default withTranslation()(AppLoader); diff --git a/frontend/src/Editor/ControlledComponentToRender.jsx b/frontend/src/Editor/ControlledComponentToRender.jsx index 54a451188b..120b47f864 100644 --- a/frontend/src/Editor/ControlledComponentToRender.jsx +++ b/frontend/src/Editor/ControlledComponentToRender.jsx @@ -1,5 +1,5 @@ import React, { useState, useCallback } from 'react'; -import { getComponentToRender } from '@/_helpers/editorHelpers'; +// import { getComponentToRender } from '@/_helpers/editorHelpers'; import _ from 'lodash'; import { getComponentsToRenders, flushComponentsToRender } from '@/_stores/editorStore'; @@ -58,7 +58,7 @@ const ComponentWrapper = React.memo(({ componentName, ...props }) => { setKey(Math.random()); }, []); - const ComponentToRender = getComponentToRender(componentName); + const ComponentToRender = <>; // getComponentToRender(componentName); if (ComponentToRender === null) return; if (componentName === 'Form') { diff --git a/frontend/src/Editor/Viewer/PreviewSettings.jsx b/frontend/src/Editor/Viewer/PreviewSettings.jsx index 3103be3e16..a4b20b5e61 100644 --- a/frontend/src/Editor/Viewer/PreviewSettings.jsx +++ b/frontend/src/Editor/Viewer/PreviewSettings.jsx @@ -15,6 +15,7 @@ import { useEditorStore } from '@/_stores/editorStore'; import Cross from '@/_ui/Icon/solidIcons/Cross'; import { checkIfLicenseNotValid } from '@/_helpers/appUtils'; import EnvironmentManager from '@/Editor/Header/EnvironmentManager'; +import { useAppType } from '@/AppBuilder/_contexts/ModuleContext'; const PreviewSettings = ({ isMobileLayout, @@ -23,6 +24,7 @@ const PreviewSettings = ({ showHeader, darkMode, }) => { + const { appType } = useAppType(); const { featureAccess, currentAppEnvironment, setCurrentAppEnvironmentId } = useEditorStore( (state) => ({ featureAccess: state?.featureAccess, @@ -84,9 +86,9 @@ const PreviewSettings = ({
Preview settings - {editingVersion && _renderAppVersionsManager()} + {editingVersion && appType !== 'module' && _renderAppVersionsManager()}
- {editingVersion && _renderEnvironmentManager()} + {editingVersion && appType !== 'module' && _renderEnvironmentManager()} diff --git a/frontend/src/HomePage/AppMenu.jsx b/frontend/src/HomePage/AppMenu.jsx index 493f5d66b4..05281de413 100644 --- a/frontend/src/HomePage/AppMenu.jsx +++ b/frontend/src/HomePage/AppMenu.jsx @@ -19,6 +19,7 @@ export const AppMenu = function AppMenu({ appCreationMode, }) { const { t } = useTranslation(); + const isModuleApp = appType === 'module'; const Field = ({ text, onClick, customClass }) => { const closeMenu = () => { document.body.click(); @@ -81,7 +82,7 @@ export const AppMenu = function AppMenu({ )} )} - {canUpdateApp && canCreateApp && appType !== 'workflow' && ( + {canUpdateApp && canCreateApp && appType !== 'workflow' && !isModuleApp && ( <> { if (_.isEmpty(currentFolder)) { - updateSidebarNAV(`All ${appType === 'workflow' ? 'workflows' : 'apps'}`); + updateSidebarNAV(`All ${appType === 'workflow' ? 'workflows' : appType === 'module' ? 'modules' : 'apps'}`); setActiveFolder({}); } else { updateSidebarNAV(currentFolder.name); @@ -104,7 +104,9 @@ export const Folders = function Folders({ setActiveFolder(folder); } folderChanged(folder); - updateSidebarNAV(folder?.name ?? 'All apps'); + updateSidebarNAV( + folder?.name ?? `All ${appType === 'front-end' ? 'apps' : appType === 'module' ? 'modules' : 'workflows'}` + ); //update the url query parameter with folder name updateFolderQuery(folder?.name); } @@ -112,7 +114,12 @@ export const Folders = function Folders({ function updateFolderQuery(name) { const search = `${name ? `?folder=${name}` : ''}`; navigate( - { pathname: `/${getWorkspaceId()}${appType === 'workflow' ? '/workflows' : ''}`, search }, + { + pathname: `/${getWorkspaceId()}${ + appType === 'workflow' ? '/workflows' : appType === 'module' ? '/modules' : '' + }`, + search, + }, { replace: true } ); } @@ -286,10 +293,12 @@ export const Folders = function Folders({ onClick={() => handleFolderChange({})} data-cy="all-applications-link" > - {t( - `${appType === 'workflow' ? 'workflowsDashboard' : 'homePage'}.foldersSection.allApplications`, - 'All apps' - )} + {appType === 'module' + ? 'All modules' + : t( + `${appType === 'workflow' ? 'workflowsDashboard' : 'homePage'}.foldersSection.allApplications`, + 'All apps' + )}
)} diff --git a/frontend/src/HomePage/Footer.jsx b/frontend/src/HomePage/Footer.jsx index d5520aea77..a95f450e1b 100644 --- a/frontend/src/HomePage/Footer.jsx +++ b/frontend/src/HomePage/Footer.jsx @@ -2,7 +2,7 @@ import React, { useState, useMemo } from 'react'; import Pagination from '@/_ui/Pagination'; import Skeleton from 'react-loading-skeleton'; -const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9 }) => { +const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9, appType }) => { const [pageCount, setPageCount] = useState(1); const totalPages = useMemo(() => { return Math.floor((count - 1) / itemsPerPage) + 1; @@ -60,7 +60,7 @@ const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9 }) ) : ( - {pageRange} of {count} apps + {pageRange} of {count} {appType === 'module' ? 'modules' : 'apps'} )}
diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index bf36da2f39..dd9c266fba 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -45,6 +45,7 @@ import { OrganizationList, UserGroupMigrationBanner, ConsultationBanner, + AppTypeTab, } from '@/modules/dashboard/components'; import CreateAppWithPrompt from '@/modules/AiBuilder/components/CreateAppWithPrompt'; import SolidIcon from '@/_ui/Icon/SolidIcons'; @@ -101,7 +102,6 @@ class HomePageComponent extends React.Component { importingGitAppOperations: {}, featuresLoaded: false, showCreateAppModal: false, - showCreateModuleModal: false, showCreateAppFromTemplateModal: false, showImportAppModal: false, showCloneAppModal: false, @@ -237,14 +237,23 @@ class HomePageComponent extends React.Component { this.fetchFolders(); }; - createApp = async (appName, type) => { + getAppType = () => { + return this.props.appType === 'module' ? 'Module' : this.props.appType === 'workflow' ? 'Workflow' : 'App'; + }; + + createApp = async (appName) => { let _self = this; _self.setState({ creatingApp: true }); + try { - const data = await appsService.createApp({ icon: sample(iconList), name: appName, type: this.props.appType }); + const data = await appsService.createApp({ + icon: sample(iconList), + name: appName, + type: this.props.appType, + }); const workspaceId = getWorkspaceId(); _self.props.navigate(`/${workspaceId}/apps/${data.id}`, { state: { commitEnabled: this.state.commitEnabled } }); - toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} created successfully!`); + toast.success(`${this.getAppType()} created successfully!`); _self.setState({ creatingApp: false }); return true; } catch (errorResponse) { @@ -263,7 +272,7 @@ class HomePageComponent extends React.Component { try { await appsService.saveApp(appId, { name: newAppName }); await this.fetchApps(this.state.currentPage, this.state.currentFolder.id); - toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} name has been updated!`); + toast.success(`${this.getAppType()} name has been updated!`); _self.setState({ renamingApp: false }); return true; } catch (errorResponse) { @@ -516,7 +525,7 @@ class HomePageComponent extends React.Component { .deleteApp(this.state.appToBeDeleted.id) // eslint-disable-next-line no-unused-vars .then((data) => { - toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} deleted successfully.`); + toast.success(`${this.getAppType()} deleted successfully.`); this.fetchApps( this.state.currentPage ? this.state.apps?.length === 1 @@ -808,11 +817,11 @@ class HomePageComponent extends React.Component { }; openCreateAppModal = () => { - this.setState({ showCreateAppModal: true, showCreateModuleModal: true }); + this.setState({ showCreateAppModal: true }); }; closeCreateAppModal = () => { - this.setState({ showCreateAppModal: false, showCreateModuleModal: false }); + this.setState({ showCreateAppModal: false }); }; openImportAppModal = async () => { @@ -891,7 +900,6 @@ class HomePageComponent extends React.Component { importingGitAppOperations, featuresLoaded, showCreateAppModal, - showCreateModuleModal, showImportAppModal, fileContent, fileName, @@ -907,14 +915,18 @@ class HomePageComponent extends React.Component { missingGroups, missingGroupsExpanded, } = this.state; + + const invalidLicense = featureAccess?.licenseStatus?.isExpired || !featureAccess?.licenseStatus?.isLicenseValid; + // const invalidLicense = false; + const modalConfigs = { create: { modalType: 'create', closeModal: this.closeCreateAppModal, - processApp: (name) => this.createApp(name, showCreateAppModal ? 'front-end' : 'module'), + processApp: (name) => this.createApp(name), show: this.openCreateAppModal, - title: this.props.appType === 'workflow' ? 'Create workflow' : 'Create app', - actionButton: this.props.appType === 'workflow' ? '+ Create workflow' : '+ Create app', + title: `Create ${this.getAppType().toLocaleLowerCase()}`, + actionButton: `+ Create ${this.getAppType().toLocaleLowerCase()}`, actionLoadingButton: 'Creating', appType: this.props.appType, }, @@ -969,7 +981,6 @@ class HomePageComponent extends React.Component { @@ -1301,6 +1312,7 @@ class HomePageComponent extends React.Component { )}
+ {this.canCreateApp() && (
= 100} disabled={ - this.props.appType === 'front-end' - ? appsLimit?.percentage >= 100 + this.props.appType === 'front-end' || this.props.appType === 'module' + ? appsLimit?.percentage >= 100 || (this.props.appType === 'module' && invalidLicense) : workflowInstanceLevelLimit.percentage >= 100 || workflowWorkspaceLevelLimit.percentage >= 100 } className={`create-new-app-button col-11 ${creatingApp ? 'btn-loading' : ''}`} - onClick={() => this.setState({ showCreateAppModal: true })} + onClick={() => + this.setState({ + showCreateAppModal: true, + }) + } data-cy="create-new-app-button" > {isImportingApp && ( )} - {this.props.t( - `${ - this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage' - }.header.createNewApplication`, - 'Create new app' - )} + {this.props.appType === 'module' + ? 'Create new module' + : this.props.t( + `${ + this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage' + }.header.createNewApplication`, + 'Create new app' + )} - {this.props.appType !== 'workflow' && ( + {this.props.appType !== 'workflow' && this.props.appType !== 'module' && ( = 100} + disabled={ + appsLimit?.percentage >= 100 || (this.props.appType === 'module' && invalidLicense) + } split className="d-inline" data-cy="import-dropdown-menu" @@ -1426,7 +1446,7 @@ class HomePageComponent extends React.Component { !appSearchKey && )} - {this.props.appType !== 'workflow' && this.canCreateApp() && ( + {this.props.appType !== 'workflow' && this.props.appType !== 'module' && this.canCreateApp() && ( )} @@ -1438,6 +1458,7 @@ class HomePageComponent extends React.Component { onSearchSubmit={this.onSearchSubmit} darkMode={this.props.darkMode} appType={this.props.appType} + disabled={this.props.appType === 'module' && invalidLicense} />
@@ -1465,31 +1486,44 @@ class HomePageComponent extends React.Component {
)} - {!isLoading && featuresLoaded && meta?.total_count === 0 && !currentFolder.id && !appSearchKey && ( - = workflowInstanceLevelLimit.total || - 100 > workflowInstanceLevelLimit.percentage >= 90 || - workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 - ? workflowInstanceLevelLimit - : workflowWorkspaceLevelLimit - } - /> - )} + {!isLoading && + featuresLoaded && + meta?.total_count === 0 && + !currentFolder.id && + !appSearchKey && + (['front-end', 'workflow'].includes(this.props.appType) ? ( + = workflowInstanceLevelLimit.total || + 100 > workflowInstanceLevelLimit.percentage >= 90 || + workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 + ? workflowInstanceLevelLimit + : workflowWorkspaceLevelLimit + } + /> + ) : ( +

+ You have not created any modules.  + + Create a module  + + to start using it within your apps. +

+ ))} {!isLoading && apps?.length === 0 && appSearchKey && (
@@ -1513,7 +1547,7 @@ class HomePageComponent extends React.Component { appActionModal={this.appActionModal} removeAppFromFolder={this.removeAppFromFolder} appType={this.props.appType} - basicPlan={featureAccess?.licenseStatus?.isExpired || !featureAccess?.licenseStatus?.isLicenseValid} + basicPlan={invalidLicense} appSearchKey={this.state.appSearchKey} /> )} @@ -1527,6 +1561,7 @@ class HomePageComponent extends React.Component { pageChanged={this.pageChanged} darkMode={this.props.darkMode} dataLoading={isLoading} + appType={this.props.appType} /> )} {/* need to review the mobile view */} diff --git a/frontend/src/MarketplacePage/InstalledPlugins.jsx b/frontend/src/MarketplacePage/InstalledPlugins.jsx index d0da8512a5..6f67f559d7 100644 --- a/frontend/src/MarketplacePage/InstalledPlugins.jsx +++ b/frontend/src/MarketplacePage/InstalledPlugins.jsx @@ -1,12 +1,13 @@ import React from 'react'; import cx from 'classnames'; -import { pluginsService, marketplaceService } from '@/_services'; +import { pluginsService, marketplaceService, globalDatasourceService } from '@/_services'; import { toast } from 'react-hot-toast'; import Spinner from '@/_ui/Spinner'; import { capitalizeFirstLetter, useTagsByPluginId } from './utils'; import { ConfirmDialog } from '@/_components'; import Icon from '@/_ui/Icon/SolidIcons'; import config from 'config'; +import Modal from '@/HomePage/Modal'; export const InstalledPlugins = () => { const [allPlugins, setAllPlugins] = React.useState([]); @@ -81,6 +82,7 @@ const InstalledPluginCard = ({ plugin, marketplacePlugin, fetchPlugins, isDevMod const [updating, setUpdating] = React.useState(false); const [isDeleteModalVisible, setDeleteModalVisibility] = React.useState(false); const [isDeletingPlugin, setDeletingPlugin] = React.useState(false); + const [showDependentQueriesInfo, setShowDependentQueriesInfo] = React.useState(false); const darkMode = localStorage.getItem('darkMode') === 'true'; const { id, name, pluginId } = plugin; @@ -140,6 +142,21 @@ const InstalledPluginCard = ({ plugin, marketplacePlugin, fetchPlugins, isDevMod toast.success(`${capitalizeFirstLetter(name)} reloaded`); }; + const getQueriesLinkedToMarketplacePlugin = (plugin) => { + globalDatasourceService + .getQueriesLinkedToMarketplacePlugin(plugin.id) + .then((data) => { + if (data?.dependent_queries) { + setShowDependentQueriesInfo(true); + } else { + setDeleteModalVisibility(true); + } + }) + .catch(({ error }) => { + toast.error(error); + }); + }; + const pluginDeleteMessage = ( <> Deleting {capitalizeFirstLetter(name)} plugin will result in the permanent removal of all @@ -150,6 +167,15 @@ const InstalledPluginCard = ({ plugin, marketplacePlugin, fetchPlugins, isDevMod return ( <> + setShowDependentQueriesInfo(false)} + > +
+ Cannot delete the {plugin?.name} plugin as it is used in the apps +
+
setDeleteModalVisibility(true)} + onClick={() => getQueriesLinkedToMarketplacePlugin(plugin)} > Remove
diff --git a/frontend/src/MarketplacePage/MarketplaceCard.jsx b/frontend/src/MarketplacePage/MarketplaceCard.jsx index 5ed9bb6e82..1a2c07cf61 100644 --- a/frontend/src/MarketplacePage/MarketplaceCard.jsx +++ b/frontend/src/MarketplacePage/MarketplaceCard.jsx @@ -17,6 +17,11 @@ export const MarketplaceCard = ({ id, name, repo, description, version, isInstal }, [isInstalled]); const installPlugin = async () => { + if (installed) { + toast.error(`${capitalizeFirstLetter(name)} is already installed.`); + return; + } + const body = { id, name, diff --git a/frontend/src/_components/ApiEndpointInput.jsx b/frontend/src/_components/ApiEndpointInput.jsx index 193413ee07..9c872af659 100644 --- a/frontend/src/_components/ApiEndpointInput.jsx +++ b/frontend/src/_components/ApiEndpointInput.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useLayoutEffect, useRef } from 'react'; import { openapiService } from '@/_services'; import Select from '@/_ui/Select'; import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles'; @@ -110,7 +110,7 @@ const ApiEndpointInput = (props) => { if (isEmpty(paths)) return []; const pathGroups = Object.keys(paths).reduce((acc, path) => { - const operations = Object.keys(paths[path]); + const operations = Object.keys(paths[path]).filter((op) => Object.keys(operationColorMapping).includes(op)); const category = path.split('/')[2]; operations.forEach((operation) => categorizeOperations(operation, path, acc, category)); return acc; @@ -135,7 +135,7 @@ const ApiEndpointInput = (props) => { {loadingSpec && (
- {props.t('stripe', 'Please wait while we load the OpenAPI specification.')} + Please wait while we load the OpenAPI specification.
)} {options && !loadingSpec && ( @@ -227,57 +227,64 @@ const RenderParameterFields = ({ parameters, type, label, options, changeParam, } const paramLabelWithDescription = (param) => { + const label = type === 'request' ? param : param.name; + const description = type === 'request' ? parameters[param]?.description : param.description; + return ( - -
- + +
+
); }; const paramLabelWithoutDescription = (param) => { - return ( - - ); - }; + const label = type === 'request' ? param : param.name; - const paramType = (param) => { return ( -
- {type === 'query' && - param?.schema?.anyOf && - param?.schema?.anyOf.map((type, i) => - i < param.schema?.anyOf.length - 1 - ? type.type.substring(0, 3).toUpperCase() + '|' - : type.type.substring(0, 3).toUpperCase() - )} - {(type === 'path' || (type === 'query' && !param?.schema?.anyOf)) && - param?.schema?.type?.substring(0, 3).toUpperCase()} - {type === 'request' && parameters[param].type?.substring(0, 3).toUpperCase()} +
+
); }; + const paramType = (param) => { + let paramTypeValue; + + if (type === 'query') { + if (param?.schema?.anyOf) { + return ( +
+ {param.schema.anyOf.map((typeObj, i) => + i < param.schema.anyOf.length - 1 + ? (typeObj.type || '').toString().substring(0, 3).toUpperCase() + '|' + : (typeObj.type || '').toString().substring(0, 3).toUpperCase() + )} +
+ ); + } + paramTypeValue = param?.schema?.type; + } else if (type === 'path') { + paramTypeValue = param?.schema?.type; + } else if (type === 'request') { + paramTypeValue = parameters[param]?.type; + } + + const displayType = Array.isArray(paramTypeValue) ? paramTypeValue[0] : paramTypeValue; + + return
{displayType?.toString().substring(0, 3).toUpperCase() || ''}
; + }; + const paramDetails = (param) => { return ( -
- {(type === 'request' && parameters[param].description) || param?.description - ? paramLabelWithDescription(param) - : paramLabelWithoutDescription(param)} - {param.required && *} +
+
+ {(type === 'request' && parameters[param].description) || param?.description + ? paramLabelWithDescription(param) + : paramLabelWithoutDescription(param)} + {param.required && *} +
{paramType(param)}
); @@ -359,3 +366,34 @@ RenderParameterFields.propTypes = { removeParam: PropTypes.func, darkMode: PropTypes.bool, }; + +const AutoWidthText = ({ value, className }) => { + const spanRef = useRef(null); + const [width, setWidth] = useState(0); + + useLayoutEffect(() => { + if (spanRef.current) { + setWidth(spanRef.current.offsetWidth); + } + }, [value]); + + return ( +
+ + {value} + + {value} +
+ ); +}; diff --git a/frontend/src/_components/AppModal.jsx b/frontend/src/_components/AppModal.jsx index 54c623dbed..72fb7cdcb5 100644 --- a/frontend/src/_components/AppModal.jsx +++ b/frontend/src/_components/AppModal.jsx @@ -10,6 +10,7 @@ import { PluginsListForAppModal } from './PluginsListForAppModal'; const APP_TYPE = { WORKFLOW: 'workflow', APP: 'app', + MODULE: 'module', }; export function AppModal({ @@ -52,6 +53,8 @@ export function AppModal({ const [isNameChanged, setIsNameChanged] = useState(false); const inputRef = useRef(null); + const appTypeName = APP_TYPE.WORKFLOW == appType ? 'Workflow' : APP_TYPE.MODULE == appType ? 'Module' : 'App'; + useEffect(() => { setIsNameChanged(newAppName?.trim() !== selectedAppName); }, [newAppName, selectedAppName]); @@ -85,7 +88,7 @@ export function AppModal({ success = await processApp(trimmedAppName); } if (success === false) { - setErrorText(`${appType == APP_TYPE.WORKFLOW ? 'Workflow' : 'App'} name already exists`); + setErrorText(`${appTypeName} name already exists`); setInfoText(''); } else { setErrorText(''); @@ -127,8 +130,6 @@ export function AppModal({ (actionButton === 'Rename app' && (!isNameChanged || newAppName.trim().length === 0 || newAppName.length > 50)) || // For rename case (actionButton !== 'Rename app' && (newAppName.length > 50 || newAppName.trim().length === 0)); - const appTypeName = APP_TYPE.WORKFLOW == appType ? 'Workflow' : 'App'; - return ( )} - {orgGit?.is_enabled && appType != APP_TYPE.WORKFLOW && ( + {orgGit?.is_enabled && appType != APP_TYPE.WORKFLOW && appType != APP_TYPE.MODULE && (
maxLetters + ? `${children.substring(0, maxLetters)}...` + : children; + return ( { const [authStatus, setAuthStatus] = useState(null); - const whiteLabelText = retrieveWhiteLabelText(); + const [whiteLabelText, setWhiteLabelText] = useState(''); + const plugin_id = selectedDataSource?.plugin?.id; const { t } = useTranslation(); + useEffect(() => { + async function fetchLabel() { + const text = await retrieveWhiteLabelText(); + setWhiteLabelText(text); + } + fetchLabel(); + }, []); function authGoogle() { const provider = 'slack'; @@ -29,7 +37,7 @@ const Slack = ({ } datasourceService - .fetchOauth2BaseUrl(provider) + .fetchOauth2BaseUrl(provider, plugin_id, {}) .then((data) => { const authUrl = `${data.url}&scope=${scope}&access_type=offline&prompt=select_account`; diff --git a/frontend/src/_components/Zendesk.jsx b/frontend/src/_components/Zendesk.jsx index 7d03035152..c61927c52e 100644 --- a/frontend/src/_components/Zendesk.jsx +++ b/frontend/src/_components/Zendesk.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import Input from '@/_ui/Input'; @@ -18,17 +18,26 @@ const Zendesk = ({ isDisabled, optionsChanged, }) => { + const [whiteLabelText, setWhiteLabelText] = useState(''); const [authStatus, setAuthStatus] = useState(null); - const whiteLabelText = retrieveWhiteLabelText(); + useEffect(() => { + async function fetchLabel() { + const text = await retrieveWhiteLabelText(); + setWhiteLabelText(text); + } + fetchLabel(); + }, []); function authZendesk() { const provider = 'zendesk'; setAuthStatus('waiting_for_url'); const scope = options?.access_type?.value === 'read' ? 'read' : 'read%20write'; + const subDomain = options?.subdomain?.value; + const client_id = options?.client_id?.value; try { - const authUrl = `https://${options?.subdomain?.value}.zendesk.com/oauth/authorizations/new?response_type=code&client_id=${options?.client_id?.value}&redirect_uri=${window.location.origin}/oauth2/authorize&scope=${scope}`; + const authUrl = `https://${subDomain}.zendesk.com/oauth/authorizations/new?response_type=code&client_id=${client_id}&redirect_uri=${window.location.origin}/oauth2/authorize&scope=${scope}`; localStorage.setItem('sourceWaitingForOAuth', 'newSource'); localStorage.setItem('currentAppEnvironmentIdForOauth', currentAppEnvironmentId); optionchanged('provider', provider).then(() => { diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 9ce0e8527a..e989164690 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -1106,42 +1106,42 @@ export function previewQuery(_ref, query, calledFromQuery = false, userSuppliedP queryStatusCode === 400 || queryStatusCode === 404 || queryStatusCode === 422: { - let errorData = {}; - switch (query.kind) { - case 'runpy': - errorData = data.data; - break; - case 'tooljetdb': - if (data?.error) { - errorData = { - message: data?.error?.message || 'Something went wrong', - description: data?.error?.message || 'Something went wrong', - status: data?.statusText || 'Failed', - data: data?.error || {}, - }; - } else { + let errorData = {}; + switch (query.kind) { + case 'runpy': + errorData = data.data; + break; + case 'tooljetdb': + if (data?.error) { + errorData = { + message: data?.error?.message || 'Something went wrong', + description: data?.error?.message || 'Something went wrong', + status: data?.statusText || 'Failed', + data: data?.error || {}, + }; + } else { + errorData = data; + errorData.description = data.errorMessage || 'Something went wrong'; + } + break; + default: errorData = data; - errorData.description = data.errorMessage || 'Something went wrong'; - } - break; - default: - errorData = data; - break; + break; + } + + onEvent(_ref, 'onDataQueryFailure', queryEvents); + useCurrentStateStore.getState().actions.setErrors({ + [query.name]: { + type: 'query', + kind: query.kind, + data: errorData, + options: options, + }, + }); + if (!calledFromQuery) setPreviewData(errorData); + + break; } - - onEvent(_ref, 'onDataQueryFailure', queryEvents); - useCurrentStateStore.getState().actions.setErrors({ - [query.name]: { - type: 'query', - kind: query.kind, - data: errorData, - options: options, - }, - }); - if (!calledFromQuery) setPreviewData(errorData); - - break; - } case queryStatus === 'needs_oauth': { const url = data.data.auth_url; // Backend generates and return sthe auth url const kind = data.data?.kind; @@ -1158,44 +1158,44 @@ export function previewQuery(_ref, query, calledFromQuery = false, userSuppliedP queryStatus === 'Created' || queryStatus === 'Accepted' || queryStatus === 'No Content': { - if (query.options.enableTransformation) { - finalData = await runTransformation( - _ref, - finalData, - query.options.transformation, - query.options.transformationLanguage, - query, - 'edit' - ); - if (finalData?.status === 'failed') { - useCurrentStateStore.getState().actions.setErrors({ - [query.name]: { - type: 'transformations', - data: finalData, - options: options, - }, - }); - onEvent(_ref, 'onDataQueryFailure', queryEvents); - setPreviewLoading(false); - resolve({ status: data.status, data: finalData }); - // console.log('Test', finalData); - if (!calledFromQuery) setPreviewData(finalData); - return; + if (query.options.enableTransformation) { + finalData = await runTransformation( + _ref, + finalData, + query.options.transformation, + query.options.transformationLanguage, + query, + 'edit' + ); + if (finalData?.status === 'failed') { + useCurrentStateStore.getState().actions.setErrors({ + [query.name]: { + type: 'transformations', + data: finalData, + options: options, + }, + }); + onEvent(_ref, 'onDataQueryFailure', queryEvents); + setPreviewLoading(false); + resolve({ status: data.status, data: finalData }); + // console.log('Test', finalData); + if (!calledFromQuery) setPreviewData(finalData); + return; + } } - } - useCurrentStateStore.getState().actions.setCurrentState({ - succededQuery: { - [query.name]: { - type: 'query', - kind: query.kind, + useCurrentStateStore.getState().actions.setCurrentState({ + succededQuery: { + [query.name]: { + type: 'query', + kind: query.kind, + }, }, - }, - }); - if (!calledFromQuery) setPreviewData(finalData); - onEvent(_ref, 'onDataQuerySuccess', queryEvents, 'edit'); - break; - } + }); + if (!calledFromQuery) setPreviewData(finalData); + onEvent(_ref, 'onDataQuerySuccess', queryEvents, 'edit'); + break; + } } setPreviewLoading(false); @@ -1411,10 +1411,10 @@ export function runQuery( }, query.kind === 'restapi' ? { - request: data.data.requestObject, - response: data.data.responseObject, - responseHeaders: data.data.responseHeaders, - } + request: data.data.requestObject, + response: data.data.responseObject, + responseHeaders: data.data.responseHeaders, + } : {} ), }, @@ -2423,22 +2423,20 @@ export function isPDFSupported() { const isChrome = browser.name === 'Chrome' && browser.major >= 92; const isEdge = browser.name === 'Edge' && browser.major >= 92; - const isSafari = browser.name === 'Safari' && browser.major >= 15 && browser.minor >= 4; // Handle minor version check for Safari + const isSafari = browser.name === 'Safari' && (browser.major > 15 || (browser.major === 15 && browser.minor >= 4)); const isFirefox = browser.name === 'Firefox' && browser.major >= 90; - // console.log('browser--', browser, isChrome || isEdge || isSafari || isFirefox); - return isChrome || isEdge || isSafari || isFirefox; } function getBrowserUserAgent(userAgent) { var regexps = { - Chrome: [/Chrome\/(\S+)/], - Firefox: [/Firefox\/(\S+)/], - MSIE: [/MSIE (\S+);/], - Opera: [/Opera\/.*?Version\/(\S+)/ /* Opera 10 */, /Opera\/(\S+)/ /* Opera 9 and older */], - Safari: [/Version\/(\S+).*?Safari\//], - }, + Chrome: [/Chrome\/(\S+)/], + Firefox: [/Firefox\/(\S+)/], + MSIE: [/MSIE (\S+);/], + Opera: [/Opera\/.*?Version\/(\S+)/ /* Opera 10 */, /Opera\/(\S+)/ /* Opera 9 and older */], + Safari: [/Version\/(\S+).*?Safari\//], + }, re, m, browser, diff --git a/frontend/src/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index c3c18a6587..2a0ed161db 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -1,58 +1,60 @@ -import { Button } from '@/Editor/Components/Button'; -import { Image } from '@/Editor/Components/Image/Image'; -import { Text } from '@/Editor/Components/Text'; -import { Table } from '@/Editor/Components/Table/Table'; -import { TextInput } from '@/Editor/Components/TextInput'; -import { NumberInput } from '@/Editor/Components/NumberInput'; -import { Container } from '@/Editor/Components/Container'; -import { Tabs } from '@/Editor/Components/Tabs'; -import { RichTextEditor } from '@/Editor/Components/RichTextEditor'; -import { DropDown } from '@/Editor/Components/DropDown'; -import { DropdownV2 } from '@/Editor/Components/DropdownV2/DropdownV2'; -import { Checkbox } from '@/Editor/Components/Checkbox'; -import { Datepicker } from '@/Editor/Components/Datepicker'; -import { DaterangePicker } from '@/Editor/Components/DaterangePicker'; -import { Multiselect } from '@/Editor/Components/Multiselect'; -import { MultiselectV2 } from '@/Editor/Components/MultiselectV2/MultiselectV2'; -import { Modal } from '@/Editor/Components/Modal'; -import { Chart } from '@/Editor/Components/Chart'; -import { Map as MapComponent } from '@/Editor/Components/Map/Map'; -import { QrScanner } from '@/Editor/Components/QrScanner/QrScanner'; -import { ToggleSwitch } from '@/Editor/Components/Toggle'; -import { ToggleSwitchV2 } from '@/Editor/Components/ToggleV2'; +// import { Button } from '@/Editor/Components/Button'; +// import { Image } from '@/Editor/Components/Image/Image'; +// import { Text } from '@/Editor/Components/Text'; +// import { Table } from '@/Editor/Components/Table/Table'; +// import { TextInput } from '@/Editor/Components/TextInput'; +// import { NumberInput } from '@/Editor/Components/NumberInput'; +// import { TextArea } from '@/Editor/Components/TextArea'; -import { RadioButton } from '@/Editor/Components/RadioButton'; -import { StarRating } from '@/Editor/Components/StarRating'; -import { Divider } from '@/Editor/Components/Divider'; -import { FilePicker } from '@/Editor/Components/FilePicker'; -import { PasswordInput } from '@/Editor/Components/PasswordInput'; -import { Calendar } from '@/Editor/Components/Calendar'; -import { Listview } from '@/Editor/Components/Listview'; -import { IFrame } from '@/Editor/Components/IFrame'; -import { CodeEditor } from '@/Editor/Components/CodeEditor'; -import { Timer } from '@/Editor/Components/Timer'; -import { Statistics } from '@/Editor/Components/Statistics'; -import { Pagination } from '@/Editor/Components/Pagination'; -import { Tags } from '@/Editor/Components/Tags'; -import { Spinner } from '@/Editor/Components/Spinner'; -import { CircularProgressBar } from '@/Editor/Components/CirularProgressbar'; -import { RangeSlider } from '@/Editor/Components/RangeSlider'; -import { Timeline } from '@/Editor/Components/Timeline'; -import { SvgImage } from '@/Editor/Components/SvgImage'; -import { Html } from '@/Editor/Components/Html'; -import { ButtonGroup } from '@/Editor/Components/ButtonGroup'; -import { CustomComponent } from '@/Editor/Components/CustomComponent/CustomComponent'; -import { VerticalDivider } from '@/Editor/Components/VerticalDivider'; -import { ColorPicker } from '@/Editor/Components/ColorPicker'; -import { KanbanBoard } from '@/Editor/Components/KanbanBoard/KanbanBoard'; -import { Kanban } from '@/Editor/Components/Kanban/Kanban'; -import { Steps } from '@/Editor/Components/Steps'; -import { TreeSelect } from '@/Editor/Components/TreeSelect'; -import { Icon } from '@/Editor/Components/Icon'; -import { Link } from '@/Editor/Components/Link/Link'; -import { Form } from '@/Editor/Components/Form/Form'; -import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox'; -import { isPDFSupported } from '@/_helpers/appUtils'; +// import { Container } from '@/Editor/Components/Container'; +// import { Tabs } from '@/Editor/Components/Tabs'; +// import { RichTextEditor } from '@/Editor/Components/RichTextEditor'; +// import { DropDown } from '@/Editor/Components/DropDown'; +// import { DropdownV2 } from '@/Editor/Components/DropdownV2/DropdownV2'; +// import { Checkbox } from '@/Editor/Components/Checkbox'; +// import { Datepicker } from '@/Editor/Components/Datepicker'; +// import { DaterangePicker } from '@/Editor/Components/DaterangePicker'; +// import { Multiselect } from '@/Editor/Components/Multiselect'; +// import { MultiselectV2 } from '@/Editor/Components/MultiselectV2/MultiselectV2'; +// import { Modal } from '@/Editor/Components/Modal'; +// import { Chart } from '@/Editor/Components/Chart'; +// import { Map as MapComponent } from '@/Editor/Components/Map/Map'; +// import { QrScanner } from '@/Editor/Components/QrScanner/QrScanner'; +// import { ToggleSwitch } from '@/Editor/Components/Toggle'; +// import { ToggleSwitchV2 } from '@/Editor/Components/ToggleV2'; + +// import { RadioButton } from '@/Editor/Components/RadioButton'; +// import { StarRating } from '@/Editor/Components/StarRating'; +// import { Divider } from '@/Editor/Components/Divider'; +// import { FilePicker } from '@/Editor/Components/FilePicker'; +// import { PasswordInput } from '@/Editor/Components/PasswordInput'; +// import { Calendar } from '@/Editor/Components/Calendar'; +// import { Listview } from '@/Editor/Components/Listview'; +// import { IFrame } from '@/Editor/Components/IFrame'; +// import { CodeEditor } from '@/Editor/Components/CodeEditor'; +// import { Timer } from '@/Editor/Components/Timer'; +// import { Statistics } from '@/Editor/Components/Statistics'; +// import { Pagination } from '@/Editor/Components/Pagination'; +// import { Tags } from '@/Editor/Components/Tags'; +// import { Spinner } from '@/Editor/Components/Spinner'; +// import { CircularProgressBar } from '@/Editor/Components/CirularProgressbar'; +// import { RangeSlider } from '@/Editor/Components/RangeSlider'; +// import { Timeline } from '@/Editor/Components/Timeline'; +// import { SvgImage } from '@/Editor/Components/SvgImage'; +// import { Html } from '@/Editor/Components/Html'; +// import { ButtonGroup } from '@/Editor/Components/ButtonGroup'; +// import { CustomComponent } from '@/Editor/Components/CustomComponent/CustomComponent'; +// import { VerticalDivider } from '@/Editor/Components/verticalDivider'; +// import { ColorPicker } from '@/Editor/Components/ColorPicker'; +// import { KanbanBoard } from '@/Editor/Components/KanbanBoard/KanbanBoard'; +// import { Kanban } from '@/Editor/Components/Kanban/Kanban'; +// import { Steps } from '@/Editor/Components/Steps'; +// import { TreeSelect } from '@/Editor/Components/TreeSelect'; +// import { Icon } from '@/Editor/Components/Icon'; +// import { Link } from '@/Editor/Components/Link'; +// import { Form } from '@/Editor/Components/Form/Form'; +// import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox'; +// import { isPDFSupported } from '@/_helpers/appUtils'; import { resolveWidgetFieldValue } from '@/_helpers/utils'; import { useEditorStore } from '@/_stores/editorStore'; import './requestIdleCallbackPolyfill'; @@ -73,70 +75,71 @@ export function memoizeFunction(func) { }; } -export const AllComponents = { - Button, - Image, - Text, - TextInput, - NumberInput, - Table, - Container, - Tabs, - RichTextEditor, - DropDown, - DropdownV2, - Checkbox, - Datepicker, - DaterangePicker, - Multiselect, - MultiselectV2, - Modal, - Chart, - Map: MapComponent, - QrScanner, - ToggleSwitch, - RadioButton, - StarRating, - Divider, - FilePicker, - PasswordInput, - Calendar, - IFrame, - CodeEditor, - Listview, - Timer, - Statistics, - Pagination, - Tags, - Spinner, - CircularProgressBar, - RangeSlider, - Timeline, - SvgImage, - Html, - ButtonGroup, - CustomComponent, - VerticalDivider, - ColorPicker, - KanbanBoard, - Kanban, - Steps, - TreeSelect, - Link, - Icon, - Form, - BoundedBox, - ToggleSwitchV2, -}; -if (isPDFSupported()) { - AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF); -} +// export const AllComponents = { +// Button, +// Image, +// Text, +// TextInput, +// NumberInput, +// Table, +// TextArea, +// Container, +// Tabs, +// RichTextEditor, +// DropDown, +// DropdownV2, +// Checkbox, +// Datepicker, +// DaterangePicker, +// Multiselect, +// MultiselectV2, +// Modal, +// Chart, +// Map: MapComponent, +// QrScanner, +// ToggleSwitch, +// RadioButton, +// StarRating, +// Divider, +// FilePicker, +// PasswordInput, +// Calendar, +// IFrame, +// CodeEditor, +// Listview, +// Timer, +// Statistics, +// Pagination, +// Tags, +// Spinner, +// CircularProgressBar, +// RangeSlider, +// Timeline, +// SvgImage, +// Html, +// ButtonGroup, +// CustomComponent, +// VerticalDivider, +// ColorPicker, +// KanbanBoard, +// Kanban, +// Steps, +// TreeSelect, +// Link, +// Icon, +// Form, +// BoundedBox, +// ToggleSwitchV2, +// }; +// if (isPDFSupported()) { +// AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF); +// } -export const getComponentToRender = (componentName) => { - const shouldHideWidget = componentName === 'PDF' && !isPDFSupported(); - if (shouldHideWidget) return null; - return AllComponents[componentName]; -}; +// export const getComponentToRender = (componentName) => { +// const shouldHideWidget = componentName === 'PDF' && !isPDFSupported(); +// if (shouldHideWidget) return null; +// return AllComponents[componentName]; +// }; export function isOnlyLayoutUpdate(diffState) { const componentDiff = Object.keys(diffState).filter((key) => diffState[key]?.layouts && !diffState[key]?.component); diff --git a/frontend/src/_helpers/routes.js b/frontend/src/_helpers/routes.js index 0f552cb955..495ca04e18 100644 --- a/frontend/src/_helpers/routes.js +++ b/frontend/src/_helpers/routes.js @@ -21,6 +21,7 @@ export const getPrivateRoute = (page, params = {}) => { workflows: '/workflows', workspace_constants: '/workspace-constants', profile_settings: '/profile-settings', + modules: '/modules', }; let url = routes[page]; diff --git a/frontend/src/_helpers/white-label/whiteLabelling.js b/frontend/src/_helpers/white-label/whiteLabelling.js index cfbbcbfc0c..c32573a96f 100644 --- a/frontend/src/_helpers/white-label/whiteLabelling.js +++ b/frontend/src/_helpers/white-label/whiteLabelling.js @@ -54,7 +54,8 @@ export async function setFaviconAndTitle(location) { 'data-sources': 'Data sources', 'audit-logs': 'Audit logs', 'account-settings': 'Profile settings', - settings: 'Profile settings', + settings: 'Settings', + 'profile-settings': 'Profile settings', login: '', signUp: '', error: '', @@ -65,9 +66,12 @@ export async function setFaviconAndTitle(location) { 'reset-password': '', 'workspace-constants': 'Workspace constants', setup: '', + '/': 'Dashboard', }; - const pageTitleKey = Object.keys(pageTitles).find((path) => location?.pathname.includes(path)); + const pageTitleKey = Object.keys(pageTitles) + .sort((a, b) => b.length - a.length) // Sort by length descending + .find((path) => location?.pathname.includes(path)); const pageTitle = pageTitles[pageTitleKey] || ''; document.title = pageTitle ? `${decodeEntities(pageTitle)} | ${whiteLabelText}` : `${decodeEntities(whiteLabelText)}`; @@ -77,6 +81,9 @@ export async function fetchAndSetWindowTitle(pageDetails) { const whiteLabelText = retrieveWhiteLabelText(); let pageTitleKey = pageDetails?.page || ''; let pageTitle = ''; + let mode = pageDetails?.mode || ''; + let isPreview = !pageDetails?.isReleased || false; + const license = pageDetails?.licenseStatus; switch (pageTitleKey) { case pageTitles.VIEWER: { const titlePrefix = pageDetails?.preview ? 'Preview - ' : ''; @@ -85,7 +92,11 @@ export async function fetchAndSetWindowTitle(pageDetails) { } case pageTitles.EDITOR: case pageTitles.WORKFLOW_EDITOR: { - pageTitle = pageDetails?.appName || 'My App'; + if (mode == 'edit') { + pageTitle = `${pageDetails?.appName}`; + } else { + pageTitle = `Preview - ${pageDetails?.appName}` || 'My App'; + } break; } default: { @@ -93,6 +104,10 @@ export async function fetchAndSetWindowTitle(pageDetails) { break; } } + if (!isPreview && mode === 'view') { + document.title = `${pageDetails?.appName} ${license ? '' : '| ToolJet'}`; + return; + } document.title = !(pageDetails?.preview === false) ? `${pageTitle} | ${whiteLabelText}` : `${pageTitle}`; } diff --git a/frontend/src/_services/apps.service.js b/frontend/src/_services/apps.service.js index 82cf4312e9..c29dea481a 100644 --- a/frontend/src/_services/apps.service.js +++ b/frontend/src/_services/apps.service.js @@ -67,18 +67,30 @@ function getAll(page, folder, searchKey, type = 'front-end') { } function createApp(body = {}) { - if (body.type === 'workflow') { - return createWorkflow(body); + const requestOptions = { + method: 'POST', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify(body), + }; + switch (body.type) { + case 'workflow': + return createWorkflow(requestOptions); + case 'module': + return createModule(requestOptions); + default: + return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse); } - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; - return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse); } -function createWorkflow(body = {}) { - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; +function createWorkflow(requestOptions) { return fetch(`${config.apiUrl}/workflows`, requestOptions).then(handleResponse); } +function createModule(requestOptions) { + return fetch(`${config.apiUrl}/modules`, requestOptions).then(handleResponse); +} + function cloneApp(id, name) { const requestOptions = { method: 'POST', diff --git a/frontend/src/_services/datasource.service.js b/frontend/src/_services/datasource.service.js index 9ea24e99d0..49a6ade912 100644 --- a/frontend/src/_services/datasource.service.js +++ b/frontend/src/_services/datasource.service.js @@ -98,7 +98,7 @@ function setOauth2Token(dataSourceId, body, current_organization_id) { function fetchOauth2BaseUrl(provider, plugin_id = null, source_options = {}) { const payload = { provider, ...(plugin_id && { plugin_id }), ...(source_options && { source_options }) }; const requestOptions = { - method: 'GET', + method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(payload), diff --git a/frontend/src/_services/globalDatasource.service.js b/frontend/src/_services/globalDatasource.service.js index aebdcca0f1..14963c19f5 100644 --- a/frontend/src/_services/globalDatasource.service.js +++ b/frontend/src/_services/globalDatasource.service.js @@ -9,6 +9,8 @@ export const globalDatasourceService = { convertToGlobal, getDataSourceByEnvironmentId, getForApp, + getQueriesLinkedToDatasource, + getQueriesLinkedToMarketplacePlugin, }; function getForApp(organizationId, appVersionId, environmentId) { @@ -68,3 +70,15 @@ function getDataSourceByEnvironmentId(dataSourceId, environmentId) { handleResponse ); } + +function getQueriesLinkedToMarketplacePlugin(pluginId) { + const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; + return fetch(`${config.apiUrl}/data-sources/dependent-queries/marketplace-plugin/${pluginId}`, requestOptions).then( + handleResponse + ); +} + +function getQueriesLinkedToDatasource(dataSourceId) { + const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; + return fetch(`${config.apiUrl}/data-sources/dependent-queries/${dataSourceId}`, requestOptions).then(handleResponse); +} diff --git a/frontend/src/_services/user.service.js b/frontend/src/_services/user.service.js index 0046ca7c11..a2017ceb22 100644 --- a/frontend/src/_services/user.service.js +++ b/frontend/src/_services/user.service.js @@ -12,6 +12,7 @@ export const userService = { getAvatar, updateAvatar, updateUserType, + updateUserTypeInstance, getUserLimits, changeUserPassword, generateUserPassword, @@ -80,6 +81,16 @@ function updateUserType(userUpdateBody) { return fetch(`${config.apiUrl}/users/user-type`, requestOptions).then(handleResponse); } +function updateUserTypeInstance(userUpdateBody) { + const requestOptions = { + method: 'PATCH', + headers: authHeader(), + body: JSON.stringify(userUpdateBody), + credentials: 'include', + }; + return fetch(`${config.apiUrl}/users/user-type/instance`, requestOptions).then(handleResponse); +} + function changePassword(currentPassword, newPassword) { const body = { currentPassword, newPassword }; const requestOptions = { method: 'PATCH', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 059841e6c7..b823e7623c 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -95,10 +95,7 @@ $btn-dark-color: #FFFFFF; } .leftsidebar-panel-header { - background-color: var(--slate3); - padding: 12px 16px; - min-height: 52px; - border-bottom: 1px solid var(--slate5); + padding: 12px 16px 0px 16px; .panel-header-container { @@ -125,6 +122,8 @@ $btn-dark-color: #FFFFFF; .page-selector-panel-body { padding: 4px; border-right: 1px solid #DFE3E6; + padding-left:16px; + padding-right:16px; &.dark-theme { border-right: 1px solid var(--slate7); diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss index e2fe92105a..6a0311b0c4 100644 --- a/frontend/src/_styles/left-sidebar.scss +++ b/frontend/src/_styles/left-sidebar.scss @@ -182,9 +182,17 @@ } } +.page { + .leftsidebar-panel-header { + margin-bottom: 8px; + } + +} + .debugger { .leftsidebar-panel-header { border-bottom: none; + margin-bottom: 8px; } .text-slate-12 { @@ -207,7 +215,7 @@ } .nav-item .nav-link { - background-color: var(--slate3) !important; + background-color: var(--base) !important; color: var(--slate11) !important; } @@ -859,4 +867,20 @@ height: calc(100% - 48px); min-height: 300px; position: relative; -} \ No newline at end of file +} + +.left-sidebar-scrollbar { + &::-webkit-scrollbar { + width: 6px; + margin-right: 3px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--interactive-default) !important; + border-radius: 3px; + } +} diff --git a/frontend/src/_styles/modules.scss b/frontend/src/_styles/modules.scss new file mode 100644 index 0000000000..dd9891df6d --- /dev/null +++ b/frontend/src/_styles/modules.scss @@ -0,0 +1,76 @@ +.apps-modules-tabs { + .nav-link { + background-color: var(--page-default); + } + + .nav-link.active { + border-bottom-width: medium !important; + } + + li.nav-item { + flex: 1; + + button { + width: 100%; + } + } + +} + +.apps-modules-navigation { + margin-bottom: 10px; + + .tab-content { + display: none; + } +} + +.apps-modules-tabs.dark-mode { + .nav-link { + background-color: inherit; + } + + border-bottom-color: #2B394A; +} + +#homePage-tab-front-end, +#homePage-tab-module { + border-radius: 0; +} + +.show-module-border { + outline: dotted 2px #CCD1D5 !important; +} + +.module-container-canvas { + >div:first-child { + height: 100%; + overflow: hidden auto; + + /* Hide scrollbar by default */ + &::-webkit-scrollbar { + width: 6px; + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: transparent; + } + + /* Show scrollbar only on hover */ + &:hover { + &::-webkit-scrollbar-thumb { + background-color: #6a727c4d; + border-radius: 3px; + } + } + + /* Firefox scrollbar support */ + scrollbar-width: thin; + scrollbar-color: transparent transparent; + + &:hover { + scrollbar-color: #6a727c4d transparent; + } + } +} \ No newline at end of file diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index da4447da2a..7dcdbafbcb 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -19,6 +19,7 @@ @import 'tailwindcss/utilities'; @import "./componentdesign.scss"; @import './pages-sidebar.scss'; +@import './modules.scss'; /* ibm-plex-sans-100 - latin */ @font-face { @@ -806,6 +807,21 @@ button { } } + .viewer .main { + height: auto !important; + + .canvas-container { + top: 0; + right: 0; + scrollbar-width: thin; + scrollbar-color: #6a727c4d transparent; + + &::-webkit-scrollbar-thumb { + background-color: #6a727c4d !important; + } + } + } + @media screen and (max-height: 450px) { .sidebar { padding-top: 15px; @@ -1552,6 +1568,13 @@ button { border-top: 1px solid var(--slate5) !important; } + &.module-editor-inspector { + .tab-content { + border-top: none !important; + } + } + + /* Hide scrollbar for Chrome, Safari and Opera */ /* Hide scrollbar for Chrome, Safari and Opera */ .tab-content::-webkit-scrollbar { display: none; @@ -2707,6 +2730,14 @@ hr { max-height: 10px; z-index: 100; min-width: 108px; + + &.module-container { + .handle-content { + cursor: move; + color: #fff; + background: #c6cad0 !important; + } + } } @@ -4119,6 +4150,7 @@ input[type="text"] { .rbc-event-label { display: none; } + background-color: var(--primary-brand) !important; border: transparent } @@ -7575,6 +7607,13 @@ tbody { .apploader { height: 100vh; + &.module-mode { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + .app-container { height: 100%; display: flex; @@ -7936,6 +7975,11 @@ tbody { display: grid; grid-template-rows: auto 1fr auto; + // Added to work with AppTypeTab component + &:has(:nth-child(4):last-child) { + grid-template-rows: auto auto 1fr auto; + } + @media only screen and (max-width: 767px) { display: none; } @@ -9138,7 +9182,8 @@ tbody { } .global-settings-app-wrapper { - max-width: 190px; + max-width: 350px; + margin-right: 10px; } .version-manager-container { @@ -10895,7 +10940,7 @@ tbody { background: var(--indigo3); border-radius: 6px; padding: 5px 10px; - + p { font-weight: 500 !important; line-height: 18px !important; @@ -12305,11 +12350,11 @@ tbody { } .design-component-inputs textarea { - + &.valid-textarea { border: 1.5px solid #519b62 !important; } - + &.invalid-textarea { border: 1.5px solid #e26367 !important; } @@ -15751,7 +15796,7 @@ tbody { /* Set the desired width */ } -textarea.tj-text-input-widget{ +textarea.tj-text-input-widget { resize: none !important; overflow-y: auto !important; } @@ -18663,8 +18708,8 @@ section.ai-message-prompt-input-wrapper { } .codebuilder-color-swatches-options { - width:100%; - height:30px; + width: 100%; + height: 30px; padding: 6px 8px; border-radius: 6px; @@ -18697,8 +18742,8 @@ section.ai-message-prompt-input-wrapper { .theme-create-btn { width: 100%; margin-bottom: 8px; - height:32px; - color:#000; + height: 32px; + color: #000; border: 1px solid var(--Border-brand-weak, #97AEFC); } @@ -18721,14 +18766,14 @@ section.ai-message-prompt-input-wrapper { border-color: var(--primary-brand); } -.multiselct-widget-option{ +.multiselct-widget-option { input:checked { background-color: var(--primary-brand); } } .multiselect-box { - .options{ + .options { input:checked { background-color: var(--primary-brand); } @@ -18737,6 +18782,7 @@ section.ai-message-prompt-input-wrapper { .timer-btn { background-color: var(--primary-brand); + &:hover { background-color: var(--primary-brand); } @@ -18775,6 +18821,7 @@ section.ai-message-prompt-input-wrapper { color: #ffffff; border: 1px solid transparent; } + .canvas-styles-header { background-color: #212325; color: #ffffff; @@ -18820,8 +18867,9 @@ section.ai-message-prompt-input-wrapper { } #inspector-tabpane-properties .accordion-header { - height:32px; + height: 32px; } + .cm-tooltip { z-index: 9999 !important; } @@ -18927,7 +18975,8 @@ section.ai-message-prompt-input-wrapper { justify-content: space-between; } - .primary-action, .secondary-action { + .primary-action, + .secondary-action { padding: 8px !important; font-size: 12px; } @@ -18943,15 +18992,15 @@ section.ai-message-prompt-input-wrapper { padding: 0; font-family: inherit; } - + .toggle-button:hover { text-decoration: underline; } - + .toggle-button .chevron { transition: transform 0.2s ease; } - + .toggle-button.expanded .chevron { transform: rotate(180deg); } diff --git a/frontend/src/_ui/Icon/solidIcons/Corners.jsx b/frontend/src/_ui/Icon/solidIcons/Corners.jsx new file mode 100644 index 0000000000..72ca376981 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Corners.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const Corners = ({ style, fill = '#C1C8CD', width = '12', height = '13', className = '', viewBox = '0 0 12 13' }) => ( + + + + + +); + +export default Corners; diff --git a/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx b/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx new file mode 100644 index 0000000000..963c4b7bef --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const EmptyStateModules = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24' }) => ( + + + + + +); + +export default EmptyStateModules; diff --git a/frontend/src/_ui/Icon/solidIcons/FileCode.jsx b/frontend/src/_ui/Icon/solidIcons/FileCode.jsx new file mode 100644 index 0000000000..ed1f3268c9 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/FileCode.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +const FileCode = ({ style, fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 12 12' }) => ( + + + + + +); + +export default FileCode; diff --git a/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx b/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx new file mode 100644 index 0000000000..dd97454175 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx @@ -0,0 +1,28 @@ +import React from 'react'; + +const RemoveFolder = ({ width = '14', fill = '#6A727C', className = '', viewBox = '0 0 14 14' }) => ( + + + + + + + + + + +); + +export default RemoveFolder; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 6227a1f0af..c6fb056b78 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -231,12 +231,16 @@ import CalendarSmall from './CalendarSmall.jsx'; import UserGroupsGrey from './UserGroupsGrey.jsx'; import AppLimitSvg from './AppLimitSvg.jsx'; import NewTabSmall from './NewTabSmall.jsx'; +import EmptyStateModules from './EmptyStateModules.jsx'; import Code from './Code.jsx'; import WorkflowV3 from './WorkflowV3.jsx'; import WorkspaceV3 from './WorkspaceV3.jsx'; import EnterpriseCrown from './EnterrpiseCrown.jsx'; +import FileCode from './FileCode.jsx'; +import Corners from './Corners.jsx'; import Moon from './Moon.jsx'; import MoreVertical01 from './MoreVertical01.jsx'; +import RemoveFolder from './RemoveFolder.jsx'; const Icon = (props) => { switch (props.name) { @@ -374,6 +378,8 @@ const Icon = (props) => { return ; case 'expand': return ; + case 'file-code': + return ; case 'file01': return ; case 'filedownload': @@ -500,6 +506,8 @@ const Icon = (props) => { return ; case 'remove01': return ; + case 'removefolder': + return ; case 'removerectangle': return ; case 'rightarrrow': @@ -536,6 +544,8 @@ const Icon = (props) => { return ; case 'comments': return ; + case 'corners': + return ; case 'share': return ; case 'shield': @@ -710,6 +720,8 @@ const Icon = (props) => { return ; case 'ai-crown': return ; + case 'empty-state-modules': + return ; case 'play01': return ; case 'moon': diff --git a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx index 978e69798f..27903c783f 100644 --- a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx +++ b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx @@ -47,6 +47,7 @@ const TextInput = ({ diff --git a/frontend/src/components/ui/Input/Index.jsx b/frontend/src/components/ui/Input/Index.jsx index 139019a198..dbb6806d6b 100644 --- a/frontend/src/components/ui/Input/Index.jsx +++ b/frontend/src/components/ui/Input/Index.jsx @@ -13,6 +13,7 @@ InputComponent.propTypes = { type: PropTypes.oneOf(['text', 'number', 'editable title', 'password', 'email']), value: PropTypes.string, onChange: PropTypes.func, + onClear: PropTypes.func, placeholder: PropTypes.string, name: PropTypes.string, id: PropTypes.string, @@ -32,6 +33,7 @@ InputComponent.propTypes = { InputComponent.defaultProps = { type: 'text', onChange: (e, validateObj) => {}, + onClear: () => {}, placeholder: '', name: '', id: '', diff --git a/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx b/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx new file mode 100644 index 0000000000..8c18f534f6 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleContainer = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleContainer, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleContainer/index.js b/frontend/src/modules/Modules/components/ModuleContainer/index.js new file mode 100644 index 0000000000..f5495b7be9 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainer/index.js @@ -0,0 +1 @@ +export { default } from './ModuleContainer'; diff --git a/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx b/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx new file mode 100644 index 0000000000..2ddb28f0d2 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleContainerBlank = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleContainerBlank, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js b/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js new file mode 100644 index 0000000000..b8954c974c --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js @@ -0,0 +1 @@ +export { default } from './ModuleContainerBlank'; diff --git a/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx b/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx new file mode 100644 index 0000000000..8782ae9ac7 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleContainerInspector = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleContainerInspector, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js b/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js new file mode 100644 index 0000000000..6417a2160e --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js @@ -0,0 +1 @@ +export { default } from './ModuleContainerInspector'; diff --git a/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx b/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx new file mode 100644 index 0000000000..3bf0d5b4e8 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleEditorBanner = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleEditorBanner, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js b/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js new file mode 100644 index 0000000000..c157c1e0b9 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js @@ -0,0 +1 @@ +export { default } from './ModuleEditorBanner'; diff --git a/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx b/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx new file mode 100644 index 0000000000..ee4d9a6a47 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleManager = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleManager, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleManager/index.js b/frontend/src/modules/Modules/components/ModuleManager/index.js new file mode 100644 index 0000000000..5a89ccc37e --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleManager/index.js @@ -0,0 +1 @@ +export { default } from './ModuleManager'; diff --git a/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx b/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx new file mode 100644 index 0000000000..2297735672 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleViewer = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleViewer, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleViewer/index.js b/frontend/src/modules/Modules/components/ModuleViewer/index.js new file mode 100644 index 0000000000..df8725725a --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewer/index.js @@ -0,0 +1 @@ +export { default } from './ModuleViewer'; diff --git a/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx b/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx new file mode 100644 index 0000000000..b5043e170a --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleViewerInspector = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleViewerInspector, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js b/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js new file mode 100644 index 0000000000..c95c5a7a46 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js @@ -0,0 +1 @@ +export { default } from './ModuleViewerInspector'; diff --git a/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx b/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx new file mode 100644 index 0000000000..7a43aa67e3 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const ModuleWidgetBox = () => { + return <>; +}; + +export default withEditionSpecificComponent(ModuleWidgetBox, 'Modules'); diff --git a/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js b/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js new file mode 100644 index 0000000000..1a903dadf7 --- /dev/null +++ b/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js @@ -0,0 +1 @@ +export { default } from './ModuleWidgetBox'; diff --git a/frontend/src/modules/Modules/components/index.js b/frontend/src/modules/Modules/components/index.js new file mode 100644 index 0000000000..0cd5f2301e --- /dev/null +++ b/frontend/src/modules/Modules/components/index.js @@ -0,0 +1,19 @@ +import ModuleContainer from './ModuleContainer'; +import ModuleViewer from './ModuleViewer'; +import ModuleContainerInspector from './ModuleContainerInspector'; +import ModuleViewerInspector from './ModuleViewerInspector'; +import ModuleWidgetBox from './ModuleWidgetBox'; +import ModuleManager from './ModuleManager'; +import ModuleEditorBanner from './ModuleEditorBanner'; +import ModuleContainerBlank from './ModuleContainerBlank'; + +export { + ModuleContainer, + ModuleViewer, + ModuleContainerInspector, + ModuleViewerInspector, + ModuleWidgetBox, + ModuleManager, + ModuleEditorBanner, + ModuleContainerBlank, +}; diff --git a/frontend/src/modules/Modules/index.js b/frontend/src/modules/Modules/index.js new file mode 100644 index 0000000000..04dab890ed --- /dev/null +++ b/frontend/src/modules/Modules/index.js @@ -0,0 +1,2 @@ +const Modules = (props) => []; +export default Modules; diff --git a/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx b/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx index 76b647f545..3d21041205 100644 --- a/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx +++ b/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx @@ -4,7 +4,7 @@ import { AppModal } from '@/_components'; const BaseAppActionModal = ({ configs, modalStates, ...props }) => { const getActiveConfig = () => { switch (true) { - case modalStates.showCreateAppModal || modalStates.showCreateModuleModal: + case modalStates.showCreateAppModal: return configs.create; case modalStates.showCloneAppModal: return configs.clone; diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx index a0e14f7bb2..f30c40b82f 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx @@ -4,12 +4,14 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { PromoteConfirmationModal } from './components'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { + const { moduleId } = useModuleContext(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment, currentEnvIndex } = useStore( (state) => ({ - isSaving: state.app.isSaving, + isSaving: state.appStore.modules[moduleId].app.isSaving, editingVersion: state.currentVersionId, selectedEnvironment: state.selectedEnvironment, environments: state.environments, diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx index cae938d130..cbc1284c06 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx @@ -9,8 +9,10 @@ import ArrowRightIcon from '@assets/images/icons/arrow-right.svg'; import '@/_styles/versions.scss'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteConfirmationModal = React.memo(({ data, onClose }) => { + const { moduleId } = useModuleContext(); const [promotingEnvironment, setPromotingEnvironment] = useState(false); const darkMode = localStorage.getItem('darkMode') === 'true' || false; const currentVersionId = useStore((state) => state.currentVersionId); @@ -22,7 +24,7 @@ const PromoteConfirmationModal = React.memo(({ data, onClose }) => { (state) => ({ promoteAppVersionAction: state.promoteAppVersionAction, selectedVersion: state.selectedVersion, - creationMode: state.app.creationMode, + creationMode: state.appStore.modules[moduleId].app.creationMode, }), shallow ); diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx index 494eaa52e9..48b77cc238 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx @@ -8,8 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { + const { moduleId } = useModuleContext(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( @@ -19,7 +21,7 @@ const ReleaseVersionButton = function DeployVersionButton() { editingVersion: state.editingVersion, isEditorFreezed: state.isEditorFreezed, updateReleasedVersionId: state.updateReleasedVersionId, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, versionToBeReleased: state.currentVersionId, // selectedVersionId: state.selectedVersion.id, }), diff --git a/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx b/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx new file mode 100644 index 0000000000..293d325c59 --- /dev/null +++ b/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +const AppTypeTab = () => { + return <>; +}; +export default withEditionSpecificComponent(AppTypeTab, 'Dashboard'); diff --git a/frontend/src/modules/dashboard/components/AppTypeTab/index.js b/frontend/src/modules/dashboard/components/AppTypeTab/index.js new file mode 100644 index 0000000000..ae18e0c4c3 --- /dev/null +++ b/frontend/src/modules/dashboard/components/AppTypeTab/index.js @@ -0,0 +1 @@ +export { default } from './AppTypeTab'; diff --git a/frontend/src/modules/dashboard/components/index.js b/frontend/src/modules/dashboard/components/index.js index ab540b5feb..d4ffff00c4 100644 --- a/frontend/src/modules/dashboard/components/index.js +++ b/frontend/src/modules/dashboard/components/index.js @@ -6,6 +6,7 @@ import SettingsMenu from './SettingsMenu'; import WorkspaceActions from './WorkspaceActions'; import ConsultationBanner from './ConsultationBanner'; import UserGroupMigrationBanner from './UserGroupMigrationBanner'; +import AppTypeTab from './AppTypeTab'; export { ImportAppMenu, @@ -16,4 +17,5 @@ export { WorkspaceActions, ConsultationBanner, UserGroupMigrationBanner, + AppTypeTab, }; diff --git a/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx b/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx index ea556429dd..9f601bb412 100644 --- a/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx +++ b/frontend/src/modules/dataSources/components/DataSourceManager/DataSourceManager.jsx @@ -21,7 +21,7 @@ import config from 'config'; import { capitalize, isEmpty } from 'lodash'; import { Card } from '@/_ui/Card'; import { withTranslation, useTranslation } from 'react-i18next'; -import { camelizeKeys, decamelizeKeys } from 'humps'; +import { camelizeKeys, decamelizeKeys, decamelize } from 'humps'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import { useAppVersionStore } from '@/_stores/appVersionStore'; @@ -249,14 +249,26 @@ class DataSourceManagerComponent extends React.Component { const scope = this.state?.scope || selectedDataSource?.scope; const parsedOptions = Object?.keys(options)?.map((key) => { - const keyMeta = dataSourceMeta.options[key]; + let keyMeta = dataSourceMeta.options[key]; + let isEncrypted = false; + if (keyMeta) { + isEncrypted = keyMeta.encrypted; + } + + // to resolve any casing mis-match + if (decamelize(key) !== key) { + const newKey = decamelize(key); + isEncrypted = dataSourceMeta.options[newKey]?.encrypted; + } + return { key: key, value: options[key].value, - encrypted: keyMeta ? keyMeta.encrypted : false, + encrypted: isEncrypted, ...(!options[key]?.value && { credential_id: options[key]?.credential_id }), }; }); + if (OAuthDs.includes(kind)) { const value = localStorage.getItem('OAuthCode'); parsedOptions.push({ key: 'code', value, encrypted: false }); diff --git a/frontend/src/modules/dataSources/components/List/index.jsx b/frontend/src/modules/dataSources/components/List/index.jsx index f3c691ad66..3ceb1864dc 100644 --- a/frontend/src/modules/dataSources/components/List/index.jsx +++ b/frontend/src/modules/dataSources/components/List/index.jsx @@ -9,6 +9,7 @@ import SolidIcon from '@/_ui/Icon/SolidIcons'; import { SearchBox } from '@/_components/SearchBox'; import { DATA_SOURCE_TYPE } from '@/_helpers/constants'; import FolderSkeleton from '@/_ui/FolderSkeleton/FolderSkeleton'; +import Modal from '@/HomePage/Modal'; export const List = ({ updateSelectedDatasource }) => { const { @@ -28,6 +29,7 @@ export const List = ({ updateSelectedDatasource }) => { const [isDeleteModalVisible, setDeleteModalVisibility] = React.useState(false); const [filteredData, setFilteredData] = useState(dataSources); const [showInput, setShowInput] = useState(false); + const [showDependentQueriesInfo, setShowDependentQueriesInfo] = useState(false); const darkMode = localStorage.getItem('darkMode') === 'true'; @@ -50,7 +52,7 @@ export const List = ({ updateSelectedDatasource }) => { setCurrentEnvironment(environments[0]); toggleDataSourceManagerModal(true); updateSelectedDatasource(selectedSource?.name); - setDeleteModalVisibility(true); + getQueriesLinkedToDatasource(selectedSource); }; const executeDataSourceDeletion = () => { @@ -74,6 +76,21 @@ export const List = ({ updateSelectedDatasource }) => { }); }; + const getQueriesLinkedToDatasource = (selectedSource) => { + globalDatasourceService + .getQueriesLinkedToDatasource(selectedSource.id) + .then((data) => { + if (data?.dependent_queries) { + setShowDependentQueriesInfo(true); + } else { + setDeleteModalVisibility(true); + } + }) + .catch(({ error }) => { + toast.error(error); + }); + }; + const cancelDeleteDataSource = () => { setDeleteModalVisibility(false); }; @@ -171,6 +188,16 @@ export const List = ({ updateSelectedDatasource }) => { )}
+ setShowDependentQueriesInfo(false)} + > +
+ Cannot delete {selectedDataSource?.name ? selectedDataSource.name : 'datasource'} as it is used in the + apps +
+
=6.0.0" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.32.1.tgz", + "integrity": "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { + "version": "18.19.103", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.103.tgz", + "integrity": "sha512-hHTHp+sEz6SxFsp+SA+Tqrua3AbmlAw+Y//aEwdHrdZkYVRWdvWD3y5uPZ0flYOkgskaFWqZ/YGFm3FaFQ0pRw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@aws-crypto/crc32": { "version": "3.0.0", "license": "Apache-2.0", @@ -177,6 +199,1070 @@ "tslib": "^2.3.1" } }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.817.0.tgz", + "integrity": "sha512-MNGwOJDQU0jpvsLLPSuPQDhPtDzFTc/k7rLmiKoPrIlgb3Y8pSF4crpJ+ZH3+xod2NWyyOVMEMQeMaKFFdMaKw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-node": "3.817.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/region-config-resolver": "3.808.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.816.0", + "@smithy/config-resolver": "^4.1.2", + "@smithy/core": "^3.3.3", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", + "@smithy/util-endpoints": "^3.0.4", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz", + "integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/region-config-resolver": "3.808.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.816.0", + "@smithy/config-resolver": "^4.1.2", + "@smithy/core": "^3.3.3", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", + "@smithy/util-endpoints": "^3.0.4", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/core": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/core": "^3.3.3", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/signature-v4": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz", + "integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz", + "integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz", + "integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz", + "integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz", + "integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz", + "integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==", + "dependencies": { + "@aws-sdk/client-sso": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/token-providers": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz", + "integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.804.0.tgz", + "integrity": "sha512-bum1hLVBrn2lJCi423Z2fMUYtsbkGI2s4N+2RI2WSjvbaVyMSv/WcejIrjkqiiMR+2Y7m5exgoKeg4/TODLDPQ==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-logger": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.804.0.tgz", + "integrity": "sha512-w/qLwL3iq0KOPQNat0Kb7sKndl9BtceigINwBU7SpkYWX9L/Lem6f8NPEKrC9Tl4wDBht3Yztub4oRTy/horJA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.804.0.tgz", + "integrity": "sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@smithy/core": "^3.3.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.808.0.tgz", + "integrity": "sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/token-providers": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz", + "integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.804.0.tgz", + "integrity": "sha512-A9qnsy9zQ8G89vrPPlNG9d1d8QcKRGqJKqwyGgS0dclJpwy6d1EWgQLIolKPl6vcFpLoe6avLOLxr+h8ur5wpg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-endpoints": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.808.0.tgz", + "integrity": "sha512-N6Lic98uc4ADB7fLWlzx+1uVnq04VgVjngZvwHoujcRg9YDhIg9dUDiTzD5VZv13g1BrPYmvYP1HhsildpGV6w==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "@smithy/util-endpoints": "^3.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.804.0.tgz", + "integrity": "sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/abort-controller": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.3.tgz", + "integrity": "sha512-AqXFf6DXnuRBXy4SoK/n1mfgHaKaq36bmkphmD1KO0nHq6xK/g9KHSW4HEsPQUBCGdIEfuJifGHwxFXPIFay9Q==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/config-resolver": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.3.tgz", + "integrity": "sha512-N5e7ofiyYDmHxnPnqF8L4KtsbSDwyxFRfDK9bp1d9OyPO4ytRLd0/XxCqi5xVaaqB65v4woW8uey6jND6zxzxQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/core": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.4.0.tgz", + "integrity": "sha512-dDYISQo7k0Ml/rXlFIjkTmTcQze/LxhtIRAEmZ6HJ/EI0inVxVEVnrUXJ7jPx6ZP0GHUhFm40iQcCgS5apXIXA==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.6", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-stream": "^4.2.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.5.tgz", + "integrity": "sha512-saEAGwrIlkb9XxX/m5S5hOtzjoJPEK6Qw2f9pYTbIsMPOFyGSXBBTw95WbOyru8A1vIS2jVCCU1Qhz50QWG3IA==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.3.tgz", + "integrity": "sha512-yBZwavI31roqTndNI7ONHqesfH01JmjJK6L3uUpZAhyAmr86LN5QiPzfyZGIxQmed8VEK2NRSQT3/JX5V1njfQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/hash-node": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.3.tgz", + "integrity": "sha512-W5Uhy6v/aYrgtjh9y0YP332gIQcwccQ+EcfWhllL0B9rPae42JngTTUpb8W6wuxaNFzqps4xq5klHckSSOy5fw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/invalid-dependency": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.3.tgz", + "integrity": "sha512-1Bo8Ur1ZGqxvwTqBmv6DZEn0rXtwJGeqiiO2/JFcCtz3nBakOqeXbJBElXJMMzd0ghe8+eB6Dkw98nMYctgizg==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-content-length": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.3.tgz", + "integrity": "sha512-NE/Zph4BP5u16bzYq2csq9qD0T6UBLeg4AuNrwNJ7Gv9uLYaGEgelZUOdRndGdMGcUfSGvNlXGb2aA2hPCwJ6g==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.7.tgz", + "integrity": "sha512-KDzM7Iajo6K7eIWNNtukykRT4eWwlHjCEsULZUaSfi/SRSBK8BPRqG5FsVfp58lUxcvre8GT8AIPIqndA0ERKw==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-serde": "^4.0.6", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-retry": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.8.tgz", + "integrity": "sha512-e2OtQgFzzlSG0uCjcJmi02QuFSRTrpT11Eh2EcqqDFy7DYriteHZJkkf+4AsxsrGDugAtPFcWBz1aq06sSX5fQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/protocol-http": "^5.1.1", + "@smithy/service-error-classification": "^4.0.4", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-retry": "^4.0.4", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-serde": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.6.tgz", + "integrity": "sha512-YECyl7uNII+jCr/9qEmCu8xYL79cU0fqjo0qxpcVIU18dAPHam/iYwcknAu4Jiyw1uN+sAx7/SMf/Kmef/Jjsg==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-stack": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.3.tgz", + "integrity": "sha512-baeV7t4jQfQtFxBADFmnhmqBmqR38dNU5cvEgHcMK/Kp3D3bEI0CouoX2Sr/rGuntR+Eg0IjXdxnGGTc6SbIkw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-config-provider": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.2.tgz", + "integrity": "sha512-SUvNup8iU1v7fmM8XPk+27m36udmGCfSz+VZP5Gb0aJ3Ne0X28K/25gnsrg3X1rWlhcnhzNUUysKW/Ied46ivQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-http-handler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.5.tgz", + "integrity": "sha512-T7QglZC1vS7SPT44/1qSIAQEx5bFKb3LfO6zw/o4Xzt1eC5HNoH1TkS4lMYA9cWFbacUhx4hRl/blLun4EOCkg==", + "dependencies": { + "@smithy/abort-controller": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/property-provider": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.3.tgz", + "integrity": "sha512-Wcn17QNdawJZcZZPBuMuzyBENVi1AXl4TdE0jvzo4vWX2x5df/oMlmr/9M5XAAC6+yae4kWZlOYIsNsgDrMU9A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/protocol-http": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.1.tgz", + "integrity": "sha512-Vsay2mzq05DwNi9jK01yCFtfvu9HimmgC7a4HTs7lhX12Sx8aWsH0mfz6q/02yspSp+lOB+Q2HJwi4IV2GKz7A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-builder": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.3.tgz", + "integrity": "sha512-UUzIWMVfPmDZcOutk2/r1vURZqavvQW0OHvgsyNV0cKupChvqg+/NKPRMaMEe+i8tP96IthMFeZOZWpV+E4RAw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.3.tgz", + "integrity": "sha512-K5M4ZJQpFCblOJ5Oyw7diICpFg1qhhR47m2/5Ef1PhGE19RaIZf50tjYFrxa6usqcuXyTiFPGo4d1geZdH4YcQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/service-error-classification": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.4.tgz", + "integrity": "sha512-W5ScbQ1bTzgH91kNEE2CvOzM4gXlDOqdow4m8vMFSIXCel2scbHwjflpVNnC60Y3F1m5i7w2gQg9lSnR+JsJAA==", + "dependencies": { + "@smithy/types": "^4.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.3.tgz", + "integrity": "sha512-vHwlrqhZGIoLwaH8vvIjpHnloShqdJ7SUPNM2EQtEox+yEDFTVQ7E+DLZ+6OhnYEgFUwPByJyz6UZaOu2tny6A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/signature-v4": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.1.tgz", + "integrity": "sha512-zy8Repr5zvT0ja+Tf5wjV/Ba6vRrhdiDcp/ww6cvqYbSEudIkziDe3uppNRlFoCViyJXdPnLcwyZdDLA4CHzSg==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/smithy-client": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.3.0.tgz", + "integrity": "sha512-DNsRA38pN6tYHUjebmwD9e4KcgqTLldYQb2gC6K+oxXYdCTxPn6wV9+FvOa6wrU2FQEnGJoi+3GULzOTKck/tg==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-endpoint": "^4.1.7", + "@smithy/middleware-stack": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-stream": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/types": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.0.tgz", + "integrity": "sha512-+1iaIQHthDh9yaLhRzaoQxRk+l9xlk+JjMFxGRhNLz+m9vKOkjNeU8QuB4w3xvzHyVR/BVlp/4AXDHjoRIkfgQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/url-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.3.tgz", + "integrity": "sha512-n5/DnosDu/tweOqUUNtUbu7eRIR4J/Wz9nL7V5kFYQQVb8VYdj7a4G5NJHCw6o21ul7CvZoJkOpdTnsQDLT0tQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.15.tgz", + "integrity": "sha512-bJJ/B8owQbHAflatSq92f9OcV8858DJBQF1Y3GRjB8psLyUjbISywszYPFw16beREHO/C3I3taW4VGH+tOuwrQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.15.tgz", + "integrity": "sha512-8CUrEW2Ni5q+NmYkj8wsgkfqoP7l4ZquptFbq92yQE66xevc4SxqP2zH6tMtN158kgBqBDsZ+qlrRwXWOjCR8A==", + "dependencies": { + "@smithy/config-resolver": "^4.1.3", + "@smithy/credential-provider-imds": "^4.0.5", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-endpoints": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.5.tgz", + "integrity": "sha512-PjDpqLk24/vAl340tmtCA++Q01GRRNH9cwL9qh46NspAX9S+IQVcK+GOzPt0GLJ6KYGyn8uOgo2kvJhiThclJw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-middleware": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.3.tgz", + "integrity": "sha512-iIsC6qZXxkD7V3BzTw3b1uK8RVC1M8WvwNxK1PKrH9FnxntCd30CSunXjL/8iJBE8Z0J14r2P69njwIpRG4FBQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-retry": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.4.tgz", + "integrity": "sha512-Aoqr9W2jDYGrI6OxljN8VmLDQIGO4VdMAUKMf9RGqLG8hn6or+K41NEy1Y5dtum9q8F7e0obYAuKl2mt/GnpZg==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.4", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.1.tgz", + "integrity": "sha512-W3IR0x5DY6iVtjj5p902oNhD+Bz7vs5S+p6tppbPa509rV9BdeXZjGuRSCtVEad9FA0Mba+tNUtUmtnSI1nwUw==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.3", + "@smithy/node-http-handler": "^4.0.5", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@aws-sdk/client-lambda": { "version": "3.635.0", "license": "Apache-2.0", @@ -1732,6 +2818,1086 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-sagemaker": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sagemaker/-/client-sagemaker-3.817.0.tgz", + "integrity": "sha512-ziLmZu31SLUfBU+L43mTKJbxO7GyezHvAFVrk0GIZslHNBBwIsImv5j1aAqkOlvn36twH2n5CXJZPrEt0nH0+A==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-node": "3.817.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/region-config-resolver": "3.808.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.816.0", + "@smithy/config-resolver": "^4.1.2", + "@smithy/core": "^3.3.3", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", + "@smithy/util-endpoints": "^3.0.4", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.3", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sso": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz", + "integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/region-config-resolver": "3.808.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.816.0", + "@smithy/config-resolver": "^4.1.2", + "@smithy/core": "^3.3.3", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", + "@smithy/util-endpoints": "^3.0.4", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/core": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/core": "^3.3.3", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/signature-v4": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz", + "integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz", + "integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz", + "integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz", + "integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz", + "integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz", + "integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==", + "dependencies": { + "@aws-sdk/client-sso": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/token-providers": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz", + "integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.804.0.tgz", + "integrity": "sha512-bum1hLVBrn2lJCi423Z2fMUYtsbkGI2s4N+2RI2WSjvbaVyMSv/WcejIrjkqiiMR+2Y7m5exgoKeg4/TODLDPQ==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-logger": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.804.0.tgz", + "integrity": "sha512-w/qLwL3iq0KOPQNat0Kb7sKndl9BtceigINwBU7SpkYWX9L/Lem6f8NPEKrC9Tl4wDBht3Yztub4oRTy/horJA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.804.0.tgz", + "integrity": "sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@smithy/core": "^3.3.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.808.0.tgz", + "integrity": "sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/token-providers": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz", + "integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/types": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.804.0.tgz", + "integrity": "sha512-A9qnsy9zQ8G89vrPPlNG9d1d8QcKRGqJKqwyGgS0dclJpwy6d1EWgQLIolKPl6vcFpLoe6avLOLxr+h8ur5wpg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-endpoints": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.808.0.tgz", + "integrity": "sha512-N6Lic98uc4ADB7fLWlzx+1uVnq04VgVjngZvwHoujcRg9YDhIg9dUDiTzD5VZv13g1BrPYmvYP1HhsildpGV6w==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "@smithy/util-endpoints": "^3.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.804.0.tgz", + "integrity": "sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/abort-controller": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.3.tgz", + "integrity": "sha512-AqXFf6DXnuRBXy4SoK/n1mfgHaKaq36bmkphmD1KO0nHq6xK/g9KHSW4HEsPQUBCGdIEfuJifGHwxFXPIFay9Q==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/config-resolver": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.3.tgz", + "integrity": "sha512-N5e7ofiyYDmHxnPnqF8L4KtsbSDwyxFRfDK9bp1d9OyPO4ytRLd0/XxCqi5xVaaqB65v4woW8uey6jND6zxzxQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/core": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.4.0.tgz", + "integrity": "sha512-dDYISQo7k0Ml/rXlFIjkTmTcQze/LxhtIRAEmZ6HJ/EI0inVxVEVnrUXJ7jPx6ZP0GHUhFm40iQcCgS5apXIXA==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.6", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-stream": "^4.2.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.5.tgz", + "integrity": "sha512-saEAGwrIlkb9XxX/m5S5hOtzjoJPEK6Qw2f9pYTbIsMPOFyGSXBBTw95WbOyru8A1vIS2jVCCU1Qhz50QWG3IA==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.3.tgz", + "integrity": "sha512-yBZwavI31roqTndNI7ONHqesfH01JmjJK6L3uUpZAhyAmr86LN5QiPzfyZGIxQmed8VEK2NRSQT3/JX5V1njfQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/hash-node": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.3.tgz", + "integrity": "sha512-W5Uhy6v/aYrgtjh9y0YP332gIQcwccQ+EcfWhllL0B9rPae42JngTTUpb8W6wuxaNFzqps4xq5klHckSSOy5fw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/invalid-dependency": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.3.tgz", + "integrity": "sha512-1Bo8Ur1ZGqxvwTqBmv6DZEn0rXtwJGeqiiO2/JFcCtz3nBakOqeXbJBElXJMMzd0ghe8+eB6Dkw98nMYctgizg==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/middleware-content-length": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.3.tgz", + "integrity": "sha512-NE/Zph4BP5u16bzYq2csq9qD0T6UBLeg4AuNrwNJ7Gv9uLYaGEgelZUOdRndGdMGcUfSGvNlXGb2aA2hPCwJ6g==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.7.tgz", + "integrity": "sha512-KDzM7Iajo6K7eIWNNtukykRT4eWwlHjCEsULZUaSfi/SRSBK8BPRqG5FsVfp58lUxcvre8GT8AIPIqndA0ERKw==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-serde": "^4.0.6", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/middleware-retry": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.8.tgz", + "integrity": "sha512-e2OtQgFzzlSG0uCjcJmi02QuFSRTrpT11Eh2EcqqDFy7DYriteHZJkkf+4AsxsrGDugAtPFcWBz1aq06sSX5fQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/protocol-http": "^5.1.1", + "@smithy/service-error-classification": "^4.0.4", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-retry": "^4.0.4", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/middleware-serde": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.6.tgz", + "integrity": "sha512-YECyl7uNII+jCr/9qEmCu8xYL79cU0fqjo0qxpcVIU18dAPHam/iYwcknAu4Jiyw1uN+sAx7/SMf/Kmef/Jjsg==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/middleware-stack": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.3.tgz", + "integrity": "sha512-baeV7t4jQfQtFxBADFmnhmqBmqR38dNU5cvEgHcMK/Kp3D3bEI0CouoX2Sr/rGuntR+Eg0IjXdxnGGTc6SbIkw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/node-config-provider": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.2.tgz", + "integrity": "sha512-SUvNup8iU1v7fmM8XPk+27m36udmGCfSz+VZP5Gb0aJ3Ne0X28K/25gnsrg3X1rWlhcnhzNUUysKW/Ied46ivQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/node-http-handler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.5.tgz", + "integrity": "sha512-T7QglZC1vS7SPT44/1qSIAQEx5bFKb3LfO6zw/o4Xzt1eC5HNoH1TkS4lMYA9cWFbacUhx4hRl/blLun4EOCkg==", + "dependencies": { + "@smithy/abort-controller": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/property-provider": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.3.tgz", + "integrity": "sha512-Wcn17QNdawJZcZZPBuMuzyBENVi1AXl4TdE0jvzo4vWX2x5df/oMlmr/9M5XAAC6+yae4kWZlOYIsNsgDrMU9A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/protocol-http": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.1.tgz", + "integrity": "sha512-Vsay2mzq05DwNi9jK01yCFtfvu9HimmgC7a4HTs7lhX12Sx8aWsH0mfz6q/02yspSp+lOB+Q2HJwi4IV2GKz7A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/querystring-builder": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.3.tgz", + "integrity": "sha512-UUzIWMVfPmDZcOutk2/r1vURZqavvQW0OHvgsyNV0cKupChvqg+/NKPRMaMEe+i8tP96IthMFeZOZWpV+E4RAw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/querystring-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.3.tgz", + "integrity": "sha512-K5M4ZJQpFCblOJ5Oyw7diICpFg1qhhR47m2/5Ef1PhGE19RaIZf50tjYFrxa6usqcuXyTiFPGo4d1geZdH4YcQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/service-error-classification": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.4.tgz", + "integrity": "sha512-W5ScbQ1bTzgH91kNEE2CvOzM4gXlDOqdow4m8vMFSIXCel2scbHwjflpVNnC60Y3F1m5i7w2gQg9lSnR+JsJAA==", + "dependencies": { + "@smithy/types": "^4.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.3.tgz", + "integrity": "sha512-vHwlrqhZGIoLwaH8vvIjpHnloShqdJ7SUPNM2EQtEox+yEDFTVQ7E+DLZ+6OhnYEgFUwPByJyz6UZaOu2tny6A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/signature-v4": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.1.tgz", + "integrity": "sha512-zy8Repr5zvT0ja+Tf5wjV/Ba6vRrhdiDcp/ww6cvqYbSEudIkziDe3uppNRlFoCViyJXdPnLcwyZdDLA4CHzSg==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/smithy-client": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.3.0.tgz", + "integrity": "sha512-DNsRA38pN6tYHUjebmwD9e4KcgqTLldYQb2gC6K+oxXYdCTxPn6wV9+FvOa6wrU2FQEnGJoi+3GULzOTKck/tg==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-endpoint": "^4.1.7", + "@smithy/middleware-stack": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-stream": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/types": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.0.tgz", + "integrity": "sha512-+1iaIQHthDh9yaLhRzaoQxRk+l9xlk+JjMFxGRhNLz+m9vKOkjNeU8QuB4w3xvzHyVR/BVlp/4AXDHjoRIkfgQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/url-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.3.tgz", + "integrity": "sha512-n5/DnosDu/tweOqUUNtUbu7eRIR4J/Wz9nL7V5kFYQQVb8VYdj7a4G5NJHCw6o21ul7CvZoJkOpdTnsQDLT0tQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.15.tgz", + "integrity": "sha512-bJJ/B8owQbHAflatSq92f9OcV8858DJBQF1Y3GRjB8psLyUjbISywszYPFw16beREHO/C3I3taW4VGH+tOuwrQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.15.tgz", + "integrity": "sha512-8CUrEW2Ni5q+NmYkj8wsgkfqoP7l4ZquptFbq92yQE66xevc4SxqP2zH6tMtN158kgBqBDsZ+qlrRwXWOjCR8A==", + "dependencies": { + "@smithy/config-resolver": "^4.1.3", + "@smithy/credential-provider-imds": "^4.0.5", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-endpoints": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.5.tgz", + "integrity": "sha512-PjDpqLk24/vAl340tmtCA++Q01GRRNH9cwL9qh46NspAX9S+IQVcK+GOzPt0GLJ6KYGyn8uOgo2kvJhiThclJw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-middleware": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.3.tgz", + "integrity": "sha512-iIsC6qZXxkD7V3BzTw3b1uK8RVC1M8WvwNxK1PKrH9FnxntCd30CSunXjL/8iJBE8Z0J14r2P69njwIpRG4FBQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-retry": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.4.tgz", + "integrity": "sha512-Aoqr9W2jDYGrI6OxljN8VmLDQIGO4VdMAUKMf9RGqLG8hn6or+K41NEy1Y5dtum9q8F7e0obYAuKl2mt/GnpZg==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.4", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-stream": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.1.tgz", + "integrity": "sha512-W3IR0x5DY6iVtjj5p902oNhD+Bz7vs5S+p6tppbPa509rV9BdeXZjGuRSCtVEad9FA0Mba+tNUtUmtnSI1nwUw==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.3", + "@smithy/node-http-handler": "^4.0.5", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-waiter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.4.tgz", + "integrity": "sha512-73aeIvHjtSB6fd9I08iFaQIGTICKpLrI3EtlWAkStVENGo1ARMq9qdoD4QwkY0RUp6A409xlgbD9NCCfCF5ieg==", + "dependencies": { + "@smithy/abort-controller": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/client-sagemaker/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@aws-sdk/client-sso": { "version": "3.282.0", "license": "Apache-2.0", @@ -3080,6 +5246,56 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.817.0.tgz", + "integrity": "sha512-+dzgWGmdmMNDdeSF+VvONN+hwqoGKX5A6Z3+siMO4CIoKWN7u5nDOx/JLjTGdVQji3522pJjJ+o9veQJNWOMRg==", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/types": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.804.0.tgz", + "integrity": "sha512-A9qnsy9zQ8G89vrPPlNG9d1d8QcKRGqJKqwyGgS0dclJpwy6d1EWgQLIolKPl6vcFpLoe6avLOLxr+h8ur5wpg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/property-provider": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.3.tgz", + "integrity": "sha512-Wcn17QNdawJZcZZPBuMuzyBENVi1AXl4TdE0jvzo4vWX2x5df/oMlmr/9M5XAAC6+yae4kWZlOYIsNsgDrMU9A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/types": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.0.tgz", + "integrity": "sha512-+1iaIQHthDh9yaLhRzaoQxRk+l9xlk+JjMFxGRhNLz+m9vKOkjNeU8QuB4w3xvzHyVR/BVlp/4AXDHjoRIkfgQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.272.0", "license": "Apache-2.0", @@ -3212,6 +5428,1050 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.817.0.tgz", + "integrity": "sha512-i6Q2MyktWHG4YG+EmLlnXTgNVjW9/yeNHSKzF55GTho5fjqfU+t9beJfuMWclanRCifamm3N5e5OCm52rVDdTQ==", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-cognito-identity": "3.817.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-node": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/config-resolver": "^4.1.2", + "@smithy/core": "^3.3.3", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz", + "integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/region-config-resolver": "3.808.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.816.0", + "@smithy/config-resolver": "^4.1.2", + "@smithy/core": "^3.3.3", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", + "@smithy/util-endpoints": "^3.0.4", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/core": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/core": "^3.3.3", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/signature-v4": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz", + "integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz", + "integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/util-stream": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz", + "integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz", + "integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.816.0", + "@aws-sdk/credential-provider-http": "3.816.0", + "@aws-sdk/credential-provider-ini": "3.817.0", + "@aws-sdk/credential-provider-process": "3.816.0", + "@aws-sdk/credential-provider-sso": "3.817.0", + "@aws-sdk/credential-provider-web-identity": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/credential-provider-imds": "^4.0.4", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz", + "integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz", + "integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==", + "dependencies": { + "@aws-sdk/client-sso": "3.817.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/token-providers": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz", + "integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.804.0.tgz", + "integrity": "sha512-bum1hLVBrn2lJCi423Z2fMUYtsbkGI2s4N+2RI2WSjvbaVyMSv/WcejIrjkqiiMR+2Y7m5exgoKeg4/TODLDPQ==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-logger": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.804.0.tgz", + "integrity": "sha512-w/qLwL3iq0KOPQNat0Kb7sKndl9BtceigINwBU7SpkYWX9L/Lem6f8NPEKrC9Tl4wDBht3Yztub4oRTy/horJA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.804.0.tgz", + "integrity": "sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@smithy/core": "^3.3.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.808.0.tgz", + "integrity": "sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/token-providers": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz", + "integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/nested-clients": "3.817.0", + "@aws-sdk/types": "3.804.0", + "@smithy/property-provider": "^4.0.2", + "@smithy/shared-ini-file-loader": "^4.0.2", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/types": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.804.0.tgz", + "integrity": "sha512-A9qnsy9zQ8G89vrPPlNG9d1d8QcKRGqJKqwyGgS0dclJpwy6d1EWgQLIolKPl6vcFpLoe6avLOLxr+h8ur5wpg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-endpoints": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.808.0.tgz", + "integrity": "sha512-N6Lic98uc4ADB7fLWlzx+1uVnq04VgVjngZvwHoujcRg9YDhIg9dUDiTzD5VZv13g1BrPYmvYP1HhsildpGV6w==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "@smithy/util-endpoints": "^3.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.804.0.tgz", + "integrity": "sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/abort-controller": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.3.tgz", + "integrity": "sha512-AqXFf6DXnuRBXy4SoK/n1mfgHaKaq36bmkphmD1KO0nHq6xK/g9KHSW4HEsPQUBCGdIEfuJifGHwxFXPIFay9Q==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/config-resolver": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.3.tgz", + "integrity": "sha512-N5e7ofiyYDmHxnPnqF8L4KtsbSDwyxFRfDK9bp1d9OyPO4ytRLd0/XxCqi5xVaaqB65v4woW8uey6jND6zxzxQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/core": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.4.0.tgz", + "integrity": "sha512-dDYISQo7k0Ml/rXlFIjkTmTcQze/LxhtIRAEmZ6HJ/EI0inVxVEVnrUXJ7jPx6ZP0GHUhFm40iQcCgS5apXIXA==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.6", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-stream": "^4.2.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/core/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.5.tgz", + "integrity": "sha512-saEAGwrIlkb9XxX/m5S5hOtzjoJPEK6Qw2f9pYTbIsMPOFyGSXBBTw95WbOyru8A1vIS2jVCCU1Qhz50QWG3IA==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.3.tgz", + "integrity": "sha512-yBZwavI31roqTndNI7ONHqesfH01JmjJK6L3uUpZAhyAmr86LN5QiPzfyZGIxQmed8VEK2NRSQT3/JX5V1njfQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/hash-node": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.3.tgz", + "integrity": "sha512-W5Uhy6v/aYrgtjh9y0YP332gIQcwccQ+EcfWhllL0B9rPae42JngTTUpb8W6wuxaNFzqps4xq5klHckSSOy5fw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/hash-node/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/invalid-dependency": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.3.tgz", + "integrity": "sha512-1Bo8Ur1ZGqxvwTqBmv6DZEn0rXtwJGeqiiO2/JFcCtz3nBakOqeXbJBElXJMMzd0ghe8+eB6Dkw98nMYctgizg==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-content-length": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.3.tgz", + "integrity": "sha512-NE/Zph4BP5u16bzYq2csq9qD0T6UBLeg4AuNrwNJ7Gv9uLYaGEgelZUOdRndGdMGcUfSGvNlXGb2aA2hPCwJ6g==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.7.tgz", + "integrity": "sha512-KDzM7Iajo6K7eIWNNtukykRT4eWwlHjCEsULZUaSfi/SRSBK8BPRqG5FsVfp58lUxcvre8GT8AIPIqndA0ERKw==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-serde": "^4.0.6", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-retry": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.8.tgz", + "integrity": "sha512-e2OtQgFzzlSG0uCjcJmi02QuFSRTrpT11Eh2EcqqDFy7DYriteHZJkkf+4AsxsrGDugAtPFcWBz1aq06sSX5fQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/protocol-http": "^5.1.1", + "@smithy/service-error-classification": "^4.0.4", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-retry": "^4.0.4", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-serde": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.6.tgz", + "integrity": "sha512-YECyl7uNII+jCr/9qEmCu8xYL79cU0fqjo0qxpcVIU18dAPHam/iYwcknAu4Jiyw1uN+sAx7/SMf/Kmef/Jjsg==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-stack": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.3.tgz", + "integrity": "sha512-baeV7t4jQfQtFxBADFmnhmqBmqR38dNU5cvEgHcMK/Kp3D3bEI0CouoX2Sr/rGuntR+Eg0IjXdxnGGTc6SbIkw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/node-config-provider": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.2.tgz", + "integrity": "sha512-SUvNup8iU1v7fmM8XPk+27m36udmGCfSz+VZP5Gb0aJ3Ne0X28K/25gnsrg3X1rWlhcnhzNUUysKW/Ied46ivQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/node-http-handler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.5.tgz", + "integrity": "sha512-T7QglZC1vS7SPT44/1qSIAQEx5bFKb3LfO6zw/o4Xzt1eC5HNoH1TkS4lMYA9cWFbacUhx4hRl/blLun4EOCkg==", + "dependencies": { + "@smithy/abort-controller": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/property-provider": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.3.tgz", + "integrity": "sha512-Wcn17QNdawJZcZZPBuMuzyBENVi1AXl4TdE0jvzo4vWX2x5df/oMlmr/9M5XAAC6+yae4kWZlOYIsNsgDrMU9A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/protocol-http": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.1.tgz", + "integrity": "sha512-Vsay2mzq05DwNi9jK01yCFtfvu9HimmgC7a4HTs7lhX12Sx8aWsH0mfz6q/02yspSp+lOB+Q2HJwi4IV2GKz7A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/querystring-builder": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.3.tgz", + "integrity": "sha512-UUzIWMVfPmDZcOutk2/r1vURZqavvQW0OHvgsyNV0cKupChvqg+/NKPRMaMEe+i8tP96IthMFeZOZWpV+E4RAw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/querystring-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.3.tgz", + "integrity": "sha512-K5M4ZJQpFCblOJ5Oyw7diICpFg1qhhR47m2/5Ef1PhGE19RaIZf50tjYFrxa6usqcuXyTiFPGo4d1geZdH4YcQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/service-error-classification": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.4.tgz", + "integrity": "sha512-W5ScbQ1bTzgH91kNEE2CvOzM4gXlDOqdow4m8vMFSIXCel2scbHwjflpVNnC60Y3F1m5i7w2gQg9lSnR+JsJAA==", + "dependencies": { + "@smithy/types": "^4.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.3.tgz", + "integrity": "sha512-vHwlrqhZGIoLwaH8vvIjpHnloShqdJ7SUPNM2EQtEox+yEDFTVQ7E+DLZ+6OhnYEgFUwPByJyz6UZaOu2tny6A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/signature-v4": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.1.tgz", + "integrity": "sha512-zy8Repr5zvT0ja+Tf5wjV/Ba6vRrhdiDcp/ww6cvqYbSEudIkziDe3uppNRlFoCViyJXdPnLcwyZdDLA4CHzSg==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/smithy-client": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.3.0.tgz", + "integrity": "sha512-DNsRA38pN6tYHUjebmwD9e4KcgqTLldYQb2gC6K+oxXYdCTxPn6wV9+FvOa6wrU2FQEnGJoi+3GULzOTKck/tg==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-endpoint": "^4.1.7", + "@smithy/middleware-stack": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-stream": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/types": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.0.tgz", + "integrity": "sha512-+1iaIQHthDh9yaLhRzaoQxRk+l9xlk+JjMFxGRhNLz+m9vKOkjNeU8QuB4w3xvzHyVR/BVlp/4AXDHjoRIkfgQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/url-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.3.tgz", + "integrity": "sha512-n5/DnosDu/tweOqUUNtUbu7eRIR4J/Wz9nL7V5kFYQQVb8VYdj7a4G5NJHCw6o21ul7CvZoJkOpdTnsQDLT0tQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-base64/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.15.tgz", + "integrity": "sha512-bJJ/B8owQbHAflatSq92f9OcV8858DJBQF1Y3GRjB8psLyUjbISywszYPFw16beREHO/C3I3taW4VGH+tOuwrQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.15.tgz", + "integrity": "sha512-8CUrEW2Ni5q+NmYkj8wsgkfqoP7l4ZquptFbq92yQE66xevc4SxqP2zH6tMtN158kgBqBDsZ+qlrRwXWOjCR8A==", + "dependencies": { + "@smithy/config-resolver": "^4.1.3", + "@smithy/credential-provider-imds": "^4.0.5", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-endpoints": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.5.tgz", + "integrity": "sha512-PjDpqLk24/vAl340tmtCA++Q01GRRNH9cwL9qh46NspAX9S+IQVcK+GOzPt0GLJ6KYGyn8uOgo2kvJhiThclJw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-middleware": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.3.tgz", + "integrity": "sha512-iIsC6qZXxkD7V3BzTw3b1uK8RVC1M8WvwNxK1PKrH9FnxntCd30CSunXjL/8iJBE8Z0J14r2P69njwIpRG4FBQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-retry": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.4.tgz", + "integrity": "sha512-Aoqr9W2jDYGrI6OxljN8VmLDQIGO4VdMAUKMf9RGqLG8hn6or+K41NEy1Y5dtum9q8F7e0obYAuKl2mt/GnpZg==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.4", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-stream": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.1.tgz", + "integrity": "sha512-W3IR0x5DY6iVtjj5p902oNhD+Bz7vs5S+p6tppbPa509rV9BdeXZjGuRSCtVEad9FA0Mba+tNUtUmtnSI1nwUw==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.3", + "@smithy/node-http-handler": "^4.0.5", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-stream/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/credential-providers/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@aws-sdk/eventstream-codec": { "version": "3.272.0", "license": "Apache-2.0", @@ -3569,6 +6829,874 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.817.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.817.0.tgz", + "integrity": "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.816.0", + "@aws-sdk/middleware-host-header": "3.804.0", + "@aws-sdk/middleware-logger": "3.804.0", + "@aws-sdk/middleware-recursion-detection": "3.804.0", + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/region-config-resolver": "3.808.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@aws-sdk/util-user-agent-browser": "3.804.0", + "@aws-sdk/util-user-agent-node": "3.816.0", + "@smithy/config-resolver": "^4.1.2", + "@smithy/core": "^3.3.3", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.6", + "@smithy/middleware-retry": "^4.1.7", + "@smithy/middleware-serde": "^4.0.5", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.14", + "@smithy/util-defaults-mode-node": "^4.0.14", + "@smithy/util-endpoints": "^3.0.4", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.3", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/core": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz", + "integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/core": "^3.3.3", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/property-provider": "^4.0.2", + "@smithy/protocol-http": "^5.1.0", + "@smithy/signature-v4": "^5.1.0", + "@smithy/smithy-client": "^4.2.6", + "@smithy/types": "^4.2.0", + "@smithy/util-middleware": "^4.0.2", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.804.0.tgz", + "integrity": "sha512-bum1hLVBrn2lJCi423Z2fMUYtsbkGI2s4N+2RI2WSjvbaVyMSv/WcejIrjkqiiMR+2Y7m5exgoKeg4/TODLDPQ==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-logger": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.804.0.tgz", + "integrity": "sha512-w/qLwL3iq0KOPQNat0Kb7sKndl9BtceigINwBU7SpkYWX9L/Lem6f8NPEKrC9Tl4wDBht3Yztub4oRTy/horJA==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.804.0.tgz", + "integrity": "sha512-zqHOrvLRdsUdN/ehYfZ9Tf8svhbiLLz5VaWUz22YndFv6m9qaAcijkpAOlKexsv3nLBMJdSdJ6GUTAeIy3BZzw==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz", + "integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==", + "dependencies": { + "@aws-sdk/core": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@aws-sdk/util-endpoints": "3.808.0", + "@smithy/core": "^3.3.3", + "@smithy/protocol-http": "^5.1.0", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.808.0.tgz", + "integrity": "sha512-9x2QWfphkARZY5OGkl9dJxZlSlYM2l5inFeo2bKntGuwg4A4YUe5h7d5yJ6sZbam9h43eBrkOdumx03DAkQF9A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/types": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.804.0.tgz", + "integrity": "sha512-A9qnsy9zQ8G89vrPPlNG9d1d8QcKRGqJKqwyGgS0dclJpwy6d1EWgQLIolKPl6vcFpLoe6avLOLxr+h8ur5wpg==", + "dependencies": { + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.808.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.808.0.tgz", + "integrity": "sha512-N6Lic98uc4ADB7fLWlzx+1uVnq04VgVjngZvwHoujcRg9YDhIg9dUDiTzD5VZv13g1BrPYmvYP1HhsildpGV6w==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "@smithy/util-endpoints": "^3.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.804.0.tgz", + "integrity": "sha512-KfW6T6nQHHM/vZBBdGn6fMyG/MgX5lq82TDdX4HRQRRuHKLgBWGpKXqqvBwqIaCdXwWHgDrg2VQups6GqOWW2A==", + "dependencies": { + "@aws-sdk/types": "3.804.0", + "@smithy/types": "^4.2.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.816.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz", + "integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.816.0", + "@aws-sdk/types": "3.804.0", + "@smithy/node-config-provider": "^4.1.1", + "@smithy/types": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/abort-controller": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.3.tgz", + "integrity": "sha512-AqXFf6DXnuRBXy4SoK/n1mfgHaKaq36bmkphmD1KO0nHq6xK/g9KHSW4HEsPQUBCGdIEfuJifGHwxFXPIFay9Q==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/config-resolver": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.3.tgz", + "integrity": "sha512-N5e7ofiyYDmHxnPnqF8L4KtsbSDwyxFRfDK9bp1d9OyPO4ytRLd0/XxCqi5xVaaqB65v4woW8uey6jND6zxzxQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/core": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.4.0.tgz", + "integrity": "sha512-dDYISQo7k0Ml/rXlFIjkTmTcQze/LxhtIRAEmZ6HJ/EI0inVxVEVnrUXJ7jPx6ZP0GHUhFm40iQcCgS5apXIXA==", + "dependencies": { + "@smithy/middleware-serde": "^4.0.6", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-stream": "^4.2.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.5.tgz", + "integrity": "sha512-saEAGwrIlkb9XxX/m5S5hOtzjoJPEK6Qw2f9pYTbIsMPOFyGSXBBTw95WbOyru8A1vIS2jVCCU1Qhz50QWG3IA==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.3.tgz", + "integrity": "sha512-yBZwavI31roqTndNI7ONHqesfH01JmjJK6L3uUpZAhyAmr86LN5QiPzfyZGIxQmed8VEK2NRSQT3/JX5V1njfQ==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/hash-node": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.3.tgz", + "integrity": "sha512-W5Uhy6v/aYrgtjh9y0YP332gIQcwccQ+EcfWhllL0B9rPae42JngTTUpb8W6wuxaNFzqps4xq5klHckSSOy5fw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/invalid-dependency": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.3.tgz", + "integrity": "sha512-1Bo8Ur1ZGqxvwTqBmv6DZEn0rXtwJGeqiiO2/JFcCtz3nBakOqeXbJBElXJMMzd0ghe8+eB6Dkw98nMYctgizg==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-content-length": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.3.tgz", + "integrity": "sha512-NE/Zph4BP5u16bzYq2csq9qD0T6UBLeg4AuNrwNJ7Gv9uLYaGEgelZUOdRndGdMGcUfSGvNlXGb2aA2hPCwJ6g==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.7.tgz", + "integrity": "sha512-KDzM7Iajo6K7eIWNNtukykRT4eWwlHjCEsULZUaSfi/SRSBK8BPRqG5FsVfp58lUxcvre8GT8AIPIqndA0ERKw==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-serde": "^4.0.6", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "@smithy/url-parser": "^4.0.3", + "@smithy/util-middleware": "^4.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-retry": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.8.tgz", + "integrity": "sha512-e2OtQgFzzlSG0uCjcJmi02QuFSRTrpT11Eh2EcqqDFy7DYriteHZJkkf+4AsxsrGDugAtPFcWBz1aq06sSX5fQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/protocol-http": "^5.1.1", + "@smithy/service-error-classification": "^4.0.4", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-retry": "^4.0.4", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-serde": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.6.tgz", + "integrity": "sha512-YECyl7uNII+jCr/9qEmCu8xYL79cU0fqjo0qxpcVIU18dAPHam/iYwcknAu4Jiyw1uN+sAx7/SMf/Kmef/Jjsg==", + "dependencies": { + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-stack": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.3.tgz", + "integrity": "sha512-baeV7t4jQfQtFxBADFmnhmqBmqR38dNU5cvEgHcMK/Kp3D3bEI0CouoX2Sr/rGuntR+Eg0IjXdxnGGTc6SbIkw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-config-provider": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.2.tgz", + "integrity": "sha512-SUvNup8iU1v7fmM8XPk+27m36udmGCfSz+VZP5Gb0aJ3Ne0X28K/25gnsrg3X1rWlhcnhzNUUysKW/Ied46ivQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/shared-ini-file-loader": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.5.tgz", + "integrity": "sha512-T7QglZC1vS7SPT44/1qSIAQEx5bFKb3LfO6zw/o4Xzt1eC5HNoH1TkS4lMYA9cWFbacUhx4hRl/blLun4EOCkg==", + "dependencies": { + "@smithy/abort-controller": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/querystring-builder": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/property-provider": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.3.tgz", + "integrity": "sha512-Wcn17QNdawJZcZZPBuMuzyBENVi1AXl4TdE0jvzo4vWX2x5df/oMlmr/9M5XAAC6+yae4kWZlOYIsNsgDrMU9A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/protocol-http": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.1.tgz", + "integrity": "sha512-Vsay2mzq05DwNi9jK01yCFtfvu9HimmgC7a4HTs7lhX12Sx8aWsH0mfz6q/02yspSp+lOB+Q2HJwi4IV2GKz7A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-builder": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.3.tgz", + "integrity": "sha512-UUzIWMVfPmDZcOutk2/r1vURZqavvQW0OHvgsyNV0cKupChvqg+/NKPRMaMEe+i8tP96IthMFeZOZWpV+E4RAw==", + "dependencies": { + "@smithy/types": "^4.3.0", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.3.tgz", + "integrity": "sha512-K5M4ZJQpFCblOJ5Oyw7diICpFg1qhhR47m2/5Ef1PhGE19RaIZf50tjYFrxa6usqcuXyTiFPGo4d1geZdH4YcQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/service-error-classification": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.4.tgz", + "integrity": "sha512-W5ScbQ1bTzgH91kNEE2CvOzM4gXlDOqdow4m8vMFSIXCel2scbHwjflpVNnC60Y3F1m5i7w2gQg9lSnR+JsJAA==", + "dependencies": { + "@smithy/types": "^4.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.3.tgz", + "integrity": "sha512-vHwlrqhZGIoLwaH8vvIjpHnloShqdJ7SUPNM2EQtEox+yEDFTVQ7E+DLZ+6OhnYEgFUwPByJyz6UZaOu2tny6A==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/signature-v4": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.1.tgz", + "integrity": "sha512-zy8Repr5zvT0ja+Tf5wjV/Ba6vRrhdiDcp/ww6cvqYbSEudIkziDe3uppNRlFoCViyJXdPnLcwyZdDLA4CHzSg==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.3", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/smithy-client": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.3.0.tgz", + "integrity": "sha512-DNsRA38pN6tYHUjebmwD9e4KcgqTLldYQb2gC6K+oxXYdCTxPn6wV9+FvOa6wrU2FQEnGJoi+3GULzOTKck/tg==", + "dependencies": { + "@smithy/core": "^3.4.0", + "@smithy/middleware-endpoint": "^4.1.7", + "@smithy/middleware-stack": "^4.0.3", + "@smithy/protocol-http": "^5.1.1", + "@smithy/types": "^4.3.0", + "@smithy/util-stream": "^4.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.0.tgz", + "integrity": "sha512-+1iaIQHthDh9yaLhRzaoQxRk+l9xlk+JjMFxGRhNLz+m9vKOkjNeU8QuB4w3xvzHyVR/BVlp/4AXDHjoRIkfgQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/url-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.3.tgz", + "integrity": "sha512-n5/DnosDu/tweOqUUNtUbu7eRIR4J/Wz9nL7V5kFYQQVb8VYdj7a4G5NJHCw6o21ul7CvZoJkOpdTnsQDLT0tQ==", + "dependencies": { + "@smithy/querystring-parser": "^4.0.3", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.15.tgz", + "integrity": "sha512-bJJ/B8owQbHAflatSq92f9OcV8858DJBQF1Y3GRjB8psLyUjbISywszYPFw16beREHO/C3I3taW4VGH+tOuwrQ==", + "dependencies": { + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.15.tgz", + "integrity": "sha512-8CUrEW2Ni5q+NmYkj8wsgkfqoP7l4ZquptFbq92yQE66xevc4SxqP2zH6tMtN158kgBqBDsZ+qlrRwXWOjCR8A==", + "dependencies": { + "@smithy/config-resolver": "^4.1.3", + "@smithy/credential-provider-imds": "^4.0.5", + "@smithy/node-config-provider": "^4.1.2", + "@smithy/property-provider": "^4.0.3", + "@smithy/smithy-client": "^4.3.0", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-endpoints": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.5.tgz", + "integrity": "sha512-PjDpqLk24/vAl340tmtCA++Q01GRRNH9cwL9qh46NspAX9S+IQVcK+GOzPt0GLJ6KYGyn8uOgo2kvJhiThclJw==", + "dependencies": { + "@smithy/node-config-provider": "^4.1.2", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-middleware": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.3.tgz", + "integrity": "sha512-iIsC6qZXxkD7V3BzTw3b1uK8RVC1M8WvwNxK1PKrH9FnxntCd30CSunXjL/8iJBE8Z0J14r2P69njwIpRG4FBQ==", + "dependencies": { + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-retry": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.4.tgz", + "integrity": "sha512-Aoqr9W2jDYGrI6OxljN8VmLDQIGO4VdMAUKMf9RGqLG8hn6or+K41NEy1Y5dtum9q8F7e0obYAuKl2mt/GnpZg==", + "dependencies": { + "@smithy/service-error-classification": "^4.0.4", + "@smithy/types": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-stream": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.1.tgz", + "integrity": "sha512-W3IR0x5DY6iVtjj5p902oNhD+Bz7vs5S+p6tppbPa509rV9BdeXZjGuRSCtVEad9FA0Mba+tNUtUmtnSI1nwUw==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.3", + "@smithy/node-http-handler": "^4.0.5", + "@smithy/types": "^4.3.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@aws-sdk/node-config-provider": { "version": "3.272.0", "license": "Apache-2.0", @@ -4867,19 +8995,19 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "engines": { - "node": ">=14" - } - }, "node_modules/@gar/promisify": { "version": "1.1.3", "dev": true, "license": "MIT" }, + "node_modules/@google/generative-ai": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz", + "integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@grpc/grpc-js": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.2.tgz", @@ -5563,15 +9691,6 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -5950,6 +10069,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@mistralai/mistralai": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.7.0.tgz", + "integrity": "sha512-yM12kf1mGxSBCZWVvSA8gMvLG1lZ+MilvHUJskU4QWVWc+uYOgupZPRgDarPerzEp6/jm9XDR/rCO7U3ElNAOg==", + "dependencies": { + "zod-to-json-schema": "^3.24.1" + }, + "peerDependencies": { + "zod": ">= 3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -7503,6 +11633,37 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@qdrant/js-client-rest": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.14.1.tgz", + "integrity": "sha512-CkCCTDc4gCXq+hhjB3yDw9Hs/PxCJ0bKqk/LjAAmuL9+nDm/RPue4C/tGOIMlzouTQ2l6J6t+JPeM//j38VFug==", + "dependencies": { + "@qdrant/openapi-typescript-fetch": "1.2.6", + "@sevinf/maybe": "0.5.0", + "undici": "^6.0.0" + }, + "engines": { + "node": ">=18.17.0", + "pnpm": ">=8" + }, + "peerDependencies": { + "typescript": ">=4.7" + } + }, + "node_modules/@qdrant/openapi-typescript-fetch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@qdrant/openapi-typescript-fetch/-/openapi-typescript-fetch-1.2.6.tgz", + "integrity": "sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==", + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8" + } + }, + "node_modules/@sevinf/maybe": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sevinf/maybe/-/maybe-0.5.0.tgz", + "integrity": "sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg==" + }, "node_modules/@sigstore/bundle": { "version": "1.1.0", "dev": true, @@ -7636,6 +11797,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "2.0.0", "dev": true, @@ -8365,10 +12537,37 @@ "@supabase/storage-js": "2.5.5" } }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tooljet-marketplace/anthropic": { + "resolved": "plugins/anthropic", + "link": true + }, "node_modules/@tooljet-marketplace/awsredshift": { "resolved": "plugins/awsredshift", "link": true }, + "node_modules/@tooljet-marketplace/azurerepos": { + "resolved": "plugins/azurerepos", + "link": true + }, + "node_modules/@tooljet-marketplace/clickup": { + "resolved": "plugins/clickup", + "link": true + }, + "node_modules/@tooljet-marketplace/cohere": { + "resolved": "plugins/cohere", + "link": true + }, "node_modules/@tooljet-marketplace/common": { "resolved": "plugins/common", "link": true @@ -8377,6 +12576,10 @@ "resolved": "plugins/engagespot", "link": true }, + "node_modules/@tooljet-marketplace/gemini": { + "resolved": "plugins/gemini", + "link": true + }, "node_modules/@tooljet-marketplace/github": { "resolved": "plugins/github", "link": true @@ -8385,6 +12588,14 @@ "resolved": "plugins/harperdb", "link": true }, + "node_modules/@tooljet-marketplace/huggingface": { + "resolved": "plugins/hugging_face", + "link": true + }, + "node_modules/@tooljet-marketplace/mistral": { + "resolved": "plugins/mistral_ai", + "link": true + }, "node_modules/@tooljet-marketplace/openai": { "resolved": "plugins/openai", "link": true @@ -8409,6 +12620,10 @@ "resolved": "plugins/presto", "link": true }, + "node_modules/@tooljet-marketplace/qdrant": { + "resolved": "plugins/qdrant", + "link": true + }, "node_modules/@tooljet-marketplace/s3": { "resolved": "plugins/s3", "link": true @@ -8429,6 +12644,10 @@ "resolved": "plugins/textract", "link": true }, + "node_modules/@tooljet-marketplace/weaviate": { + "resolved": "plugins/weaviate", + "link": true + }, "node_modules/@tooljet-plugins/aws-lambda": { "resolved": "plugins/aws-lambda", "link": true @@ -8536,6 +12755,17 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "dev": true, @@ -8545,6 +12775,11 @@ "@types/node": "*" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "dev": true, @@ -8580,6 +12815,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/minimatch": { "version": "3.0.5", "dev": true, @@ -8622,6 +12865,14 @@ "license": "MIT", "peer": true }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.3.13", "dev": true, @@ -8632,6 +12883,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, "node_modules/@types/ws": { "version": "8.5.10", "license": "MIT", @@ -9200,8 +13456,10 @@ } }, "node_modules/aws-sdk": { - "version": "2.1511.0", - "license": "Apache-2.0", + "version": "2.1692.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1692.0.tgz", + "integrity": "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==", + "hasInstallScript": true, "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -9212,15 +13470,16 @@ "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", - "xml2js": "0.5.0" + "xml2js": "0.6.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/aws-sdk/node_modules/xml2js": { - "version": "0.5.0", - "license": "MIT", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -9231,7 +13490,8 @@ }, "node_modules/aws-sdk/node_modules/xmlbuilder": { "version": "11.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "engines": { "node": ">=4.0" } @@ -9254,6 +13514,18 @@ "follow-redirects": "^1.14.0" } }, + "node_modules/azure-devops-node-api": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-14.1.0.tgz", + "integrity": "sha512-QhpgjH1LQ+vgDJ7oBwcmsZ3+o4ZpjLVilw0D3oJQpYpRzN+L39lk5jZDLJ464hLUgsDzWn/Ksv7zLLMKLfoBzA==", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "2.1.0" + }, + "engines": { + "node": ">= 16.0.0" + } + }, "node_modules/babel-jest": { "version": "29.5.0", "dev": true, @@ -9692,6 +13964,45 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.2", "license": "MIT", @@ -9703,6 +14014,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "dev": true, @@ -9883,6 +14221,17 @@ "node": ">=0.10.0" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cmd-shim": { "version": "5.0.0", "dev": true, @@ -9904,6 +14253,267 @@ "node": ">= 0.12.0" } }, + "node_modules/cohere-ai": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/cohere-ai/-/cohere-ai-7.17.1.tgz", + "integrity": "sha512-GI/uWVYYGIN3gdjJRlbjEaLJNJVXsUJyOlPqwBWgAmK18kP4CJoErxKwU0aLe3tHHOBcC2RqXe6PmGO0dz7dpQ==", + "dependencies": { + "@aws-sdk/client-sagemaker": "^3.583.0", + "@aws-sdk/credential-providers": "^3.583.0", + "@aws-sdk/protocol-http": "^3.374.0", + "@aws-sdk/signature-v4": "^3.374.0", + "convict": "^6.2.4", + "form-data": "^4.0.0", + "form-data-encoder": "^4.0.2", + "formdata-node": "^6.0.3", + "js-base64": "3.7.2", + "node-fetch": "2.7.0", + "qs": "6.11.2", + "readable-stream": "^4.5.2", + "url-join": "4.0.1" + } + }, + "node_modules/cohere-ai/node_modules/@aws-sdk/protocol-http": { + "version": "3.374.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.374.0.tgz", + "integrity": "sha512-9WpRUbINdGroV3HiZZIBoJvL2ndoWk39OfwxWs2otxByppJZNN14bg/lvCx5e8ggHUti7IBk5rb0nqQZ4m05pg==", + "deprecated": "This package has moved to @smithy/protocol-http", + "dependencies": { + "@smithy/protocol-http": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@aws-sdk/signature-v4": { + "version": "3.374.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.374.0.tgz", + "integrity": "sha512-2xLJvSdzcZZAg0lsDLUAuSQuihzK0dcxIK7WmfuJeF7DGKJFmp9czQmz5f3qiDz6IDQzvgK1M9vtJSVCslJbyQ==", + "deprecated": "This package has moved to @smithy/signature-v4", + "dependencies": { + "@smithy/signature-v4": "^1.0.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/eventstream-codec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.1.0.tgz", + "integrity": "sha512-3tEbUb8t8an226jKB6V/Q2XU/J53lCwCzULuBPEaF4JjSh+FlCMp7TmogE/Aij5J9DwlsZ4VAD/IRDuQ/0ZtMw==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^1.2.0", + "@smithy/util-hex-encoding": "^1.1.0", + "tslib": "^2.5.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/is-array-buffer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.1.0.tgz", + "integrity": "sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/protocol-http": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.2.0.tgz", + "integrity": "sha512-GfGfruksi3nXdFok5RhgtOnWe5f6BndzYfmEXISD+5gAGdayFGpjWu5pIqIweTudMtse20bGbc+7MFZXT1Tb8Q==", + "dependencies": { + "@smithy/types": "^1.2.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/signature-v4": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.1.0.tgz", + "integrity": "sha512-fDo3m7YqXBs7neciOePPd/X9LPm5QLlDMdIC4m1H6dgNLnXfLMFNIxEfPyohGA8VW9Wn4X8lygnPSGxDZSmp0Q==", + "dependencies": { + "@smithy/eventstream-codec": "^1.1.0", + "@smithy/is-array-buffer": "^1.1.0", + "@smithy/types": "^1.2.0", + "@smithy/util-hex-encoding": "^1.1.0", + "@smithy/util-middleware": "^1.1.0", + "@smithy/util-uri-escape": "^1.1.0", + "@smithy/util-utf8": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/util-buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.1.0.tgz", + "integrity": "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw==", + "dependencies": { + "@smithy/is-array-buffer": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/util-hex-encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.1.0.tgz", + "integrity": "sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/util-middleware": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.1.0.tgz", + "integrity": "sha512-6hhckcBqVgjWAqLy2vqlPZ3rfxLDhFWEmM7oLh2POGvsi7j0tHkbN7w4DFhuBExVJAbJ/qqxqZdRY6Fu7/OezQ==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/util-uri-escape": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.1.0.tgz", + "integrity": "sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/@smithy/util-utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.1.0.tgz", + "integrity": "sha512-p/MYV+JmqmPyjdgyN2UxAeYDj9cBqCjp0C/NsTWnnjoZUVqoeZ6IrW915L9CAKWVECgv9lVQGc4u/yz26/bI1A==", + "dependencies": { + "@smithy/util-buffer-from": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/cohere-ai/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/cohere-ai/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/cohere-ai/node_modules/form-data-encoder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", + "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cohere-ai/node_modules/formdata-node": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", + "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cohere-ai/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/cohere-ai/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cohere-ai/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "dev": true, @@ -10185,6 +14795,26 @@ "license": "MIT", "peer": true }, + "node_modules/convict": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "yargs-parser": "^20.2.7" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/convict/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/core-js": { "version": "3.38.0", "hasInstallScript": true, @@ -10345,6 +14975,31 @@ "node": ">=0.10.0" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "0.7.0", "dev": true, @@ -10375,6 +15030,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "dev": true, @@ -10421,6 +15084,15 @@ "dev": true, "license": "ISC" }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/detect-indent": { "version": "5.0.0", "dev": true, @@ -10490,6 +15162,19 @@ "node": ">=10" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "dev": true, @@ -10570,7 +15255,6 @@ }, "node_modules/end-of-stream": { "version": "1.4.4", - "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -10619,6 +15303,33 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "license": "MIT", @@ -11296,8 +16007,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "license": "MIT" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gauge": { "version": "4.0.4", @@ -11334,12 +16049,23 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "license": "MIT", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11429,6 +16155,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.0", "dev": true, @@ -11596,15 +16334,40 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "license": "ISC" @@ -11669,6 +16432,7 @@ }, "node_modules/has": { "version": "1.0.3", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.1" @@ -11685,8 +16449,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "license": "MIT", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -11712,6 +16477,17 @@ "dev": true, "license": "ISC" }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hosted-git-info": { "version": "6.1.1", "dev": true, @@ -11739,7 +16515,6 @@ }, "node_modules/http-cache-semantics": { "version": "4.1.1", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/http-parser-js": { @@ -11772,6 +16547,29 @@ "npm": ">=1.3.7" } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "license": "MIT", @@ -13280,6 +18078,16 @@ "node": ">= 0.6.0" } }, + "node_modules/js-base64": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", + "integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==" + }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, "node_modules/js-sdsl": { "version": "4.4.0", "dev": true, @@ -13451,6 +18259,11 @@ "node": ">=4.0" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "dev": true, @@ -13599,6 +18412,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "dev": true, @@ -14102,6 +18923,11 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "license": "MIT" @@ -14175,6 +19001,14 @@ "loose-envify": "cli.js" } }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", @@ -14412,6 +19246,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/meow": { "version": "8.1.2", "dev": true, @@ -14650,6 +19492,14 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/min-indent": { "version": "1.0.1", "dev": true, @@ -14658,6 +19508,11 @@ "node": ">=4" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "node_modules/minimatch": { "version": "3.1.2", "license": "ISC", @@ -14975,8 +19830,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.9", - "license": "MIT", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -15116,6 +19972,17 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-bundled": { "version": "3.0.1", "dev": true, @@ -15689,6 +20556,17 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/octokit": { "version": "4.0.2", "license": "MIT", @@ -15973,6 +20851,14 @@ "node": ">=0.10.0" } }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, "node_modules/p-finally": { "version": "1.0.0", "dev": true, @@ -16685,6 +21571,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "dev": true, @@ -16785,6 +21679,15 @@ "version": "1.9.0", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "1.3.2", "license": "MIT" @@ -17271,6 +22174,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "dev": true, @@ -17307,6 +22215,17 @@ "node": ">=10" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "license": "MIT", @@ -17460,6 +22379,74 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "license": "ISC" @@ -18321,6 +23308,14 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "license": "Apache-2.0", @@ -18366,6 +23361,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-rest-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.1.0.tgz", + "integrity": "sha512-Nel9aPbgSzRxfs1+4GoSB4wexCF+4Axlk7OSGVQCMa+4fWcyxIsN/YNmkp0xTT2iQzMD98h8yFLav/cNaULmRA==", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "^6.10.3", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/typed-rest-client/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "dev": true, @@ -18394,15 +23418,17 @@ "node": ">=0.8.0" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "engines": { - "node": ">=14.0" + "node": ">=18.17" } }, "node_modules/undici-types": { @@ -18522,6 +23548,11 @@ "querystring": "0.2.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, "node_modules/utf8": { "version": "2.1.2", "license": "MIT" @@ -18934,6 +23965,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.25.30", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.30.tgz", + "integrity": "sha512-VolhdEtu6TJr/fzGuHA/SZ5ixvXqA6ADOG9VRcQ3rdOKmF5hkmcJbyaQjUH5BgmpA9gej++zYRX7zjSmdReIwA==", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "plugins/anthropic": { + "version": "1.0.0", + "dependencies": { + "@anthropic-ai/sdk": "^0.32.1", + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, "plugins/aws-lambda": { "name": "@tooljet-plugins/aws-lambda", "version": "1.0.0", @@ -18957,6 +24016,38 @@ "typescript": "^4.7.4" } }, + "plugins/azurerepos": { + "version": "1.0.0", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0", + "azure-devops-node-api": "^14.1.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, + "plugins/clickup": { + "version": "1.0.0", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, + "plugins/cohere": { + "version": "1.0.0", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0", + "cohere-ai": "^7.15.4" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, "plugins/common": { "name": "@tooljet-marketplace/common", "version": "1.0.0", @@ -18976,6 +24067,17 @@ "typescript": "^4.7.4" } }, + "plugins/gemini": { + "version": "1.0.0", + "dependencies": { + "@google/generative-ai": "^0.21.0", + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, "plugins/github": { "name": "@tooljet-marketplace/github", "version": "1.0.0", @@ -19000,6 +24102,16 @@ "typescript": "^4.7.4" } }, + "plugins/hugging_face": { + "version": "1.0.0", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, "plugins/jira": { "name": "@tooljet-plugins/jira", "version": "1.0.0", @@ -19009,6 +24121,17 @@ "react": "^17.0.2" } }, + "plugins/mistral_ai": { + "version": "1.0.0", + "dependencies": { + "@mistralai/mistralai": "^1.4.0", + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } + }, "plugins/openai": { "name": "@tooljet-marketplace/openai", "version": "1.0.0", @@ -19082,6 +24205,26 @@ "typescript": "^4.7.4" } }, + "plugins/qdrant": { + "version": "1.0.0", + "dependencies": { + "@qdrant/js-client-rest": "^1.12.0", + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.38.3", + "typescript": "^4.7.4" + } + }, + "plugins/qdrant/node_modules/@vercel/ncc": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.3.tgz", + "integrity": "sha512-rnK6hJBS6mwc+Bkab+PGPs9OiS0i/3kdTO+CkI8V0/VrW3vmz7O2Pxjw/owOlmo6PKEIxRSeZKv/kuL9itnpYA==", + "dev": true, + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, "plugins/s3": { "name": "@tooljet-marketplace/s3", "version": "1.0.0", @@ -19141,6 +24284,17 @@ "@vercel/ncc": "^0.34.0", "typescript": "^4.7.4" } + }, + "plugins/weaviate": { + "version": "1.0.0", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0", + "got": "^11.8.6" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } } } } \ No newline at end of file diff --git a/marketplace/package.json b/marketplace/package.json index 670dc0bf4e..5d69e411ec 100644 --- a/marketplace/package.json +++ b/marketplace/package.json @@ -9,7 +9,7 @@ "@types/jest": "^29.5.0", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", - "aws-sdk": "^2.1326.0", + "aws-sdk": "^2.1692.0", "eslint": "^8.37.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-jest": "^27.2.1", diff --git a/marketplace/plugins/clickup/.gitignore b/marketplace/plugins/clickup/.gitignore new file mode 100644 index 0000000000..23e6609462 --- /dev/null +++ b/marketplace/plugins/clickup/.gitignore @@ -0,0 +1,5 @@ +node_modules +lib/*.d.* +lib/*.js +lib/*.js.map +dist/* \ No newline at end of file diff --git a/marketplace/plugins/clickup/README.md b/marketplace/plugins/clickup/README.md new file mode 100644 index 0000000000..d6030e2327 --- /dev/null +++ b/marketplace/plugins/clickup/README.md @@ -0,0 +1,4 @@ + +# ClickUp + +Documentation on: https://docs.tooljet.com/docs/data-sources/clickup \ No newline at end of file diff --git a/marketplace/plugins/clickup/__tests__/index.js b/marketplace/plugins/clickup/__tests__/index.js new file mode 100644 index 0000000000..6875592b04 --- /dev/null +++ b/marketplace/plugins/clickup/__tests__/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const clickup = require('../lib'); + +describe('clickup', () => { + it.todo('needs tests'); +}); diff --git a/marketplace/plugins/clickup/lib/icon.svg b/marketplace/plugins/clickup/lib/icon.svg new file mode 100644 index 0000000000..2f983484c1 --- /dev/null +++ b/marketplace/plugins/clickup/lib/icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/marketplace/plugins/clickup/lib/index.ts b/marketplace/plugins/clickup/lib/index.ts new file mode 100644 index 0000000000..dc6e051bae --- /dev/null +++ b/marketplace/plugins/clickup/lib/index.ts @@ -0,0 +1,114 @@ +import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-marketplace/common'; +import { SourceOptions } from './types'; +import got, { Headers } from 'got'; + +export default class Clickup implements QueryService { + authHeader(token: string): Headers { + return { Authorization: token }; + } + + async run(sourceOptions: SourceOptions, queryOptions: any, dataSourceId: string): Promise { + const operation = queryOptions.operation; + const apiKey = sourceOptions.apiKey; + const baseUrl = 'https://api.clickup.com/api'; + const path = queryOptions['path']; + + const pathParams = queryOptions['params']['path']; + const queryParams = queryOptions['params']['query']; + const bodyParams = queryOptions['params']['request']; + + // Replace path params in URL + let modifiedPath = path; + for (const param of Object.keys(pathParams)) { + modifiedPath = modifiedPath.replace(`{${param}}`, pathParams[param]); + } + + const url = `${baseUrl}${modifiedPath}`; + + try { + let response; + + if (operation === 'get' || operation === 'delete') { + response = await got(url, { + method: operation, + headers: this.authHeader(apiKey), + searchParams: queryParams, + }); + } else { + // post, put, patch operations + const resolvedBodyParams = this.resolveBodyparams(bodyParams); + response = await got(url, { + method: operation, + headers: this.authHeader(apiKey), + json: resolvedBodyParams, + searchParams: queryParams, + }); + } + + return { + status: 'ok', + data: JSON.parse(response.body), + }; + } catch (err) { + const errorMessage = err.message || 'An unknown error occurred'; + const errorDetails: any = {}; + + if (err.response) { + const { statusCode, body } = err.response; + errorDetails.statusCode = statusCode; + + try { + const parsedBody = JSON.parse(body); + errorDetails.error = parsedBody.err || null; + errorDetails.code = parsedBody.ECODE || null; + } catch (parseError) { + errorDetails.rawBody = body; + } + } + + throw new QueryError('Query could not be completed', errorMessage, errorDetails); + } + } + + async testConnection(sourceOptions: SourceOptions): Promise { + const apiKey = sourceOptions.apiKey; + + try { + const response = await got('https://api.clickup.com/api/v2/user', { + headers: this.authHeader(apiKey), + }); + + const data = JSON.parse(response.body); + + if (data?.user?.id) { + return { + status: 'ok', + }; + } else { + throw new QueryError('User information not found', 'Invalid API key or insufficient permissions', {}); + } + } catch (error) { + throw new QueryError('Connection could not be established', error.response?.body || error.message, {}); + } + } + + private resolveBodyparams(bodyParams: object): object { + if (typeof bodyParams === 'string') { + return bodyParams; + } + + const expectedResult = {}; + + for (const key of Object.keys(bodyParams)) { + if (typeof bodyParams[key] === 'object') { + for (const subKey of Object.keys(bodyParams[key])) { + expectedResult[`${key}[${subKey}]`] = bodyParams[key][subKey]; + } + } else { + expectedResult[key] = bodyParams[key]; + } + } + + return expectedResult; + } +} diff --git a/marketplace/plugins/clickup/lib/manifest.json b/marketplace/plugins/clickup/lib/manifest.json new file mode 100644 index 0000000000..37eaad21a6 --- /dev/null +++ b/marketplace/plugins/clickup/lib/manifest.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json", + "title": "ClickUp datasource", + "description": "Clickup plugin for task, list, and doc management", + "type": "api", + "source": { + "name": "ClickUp", + "kind": "clickup", + "exposedVariables": { + "isLoading": false, + "data": {}, + "rawData": {} + }, + "options": { + "apiKey": { + "type": "string", + "encrypted": true + } + } + }, + "defaults": {}, + "properties": { + "apiKey": { + "label": "API Key", + "key": "apiKey", + "type": "password", + "description": "Enter your Personal API Token" + } + }, + "required": [ + "apiKey" + ] +} \ No newline at end of file diff --git a/marketplace/plugins/clickup/lib/operations.json b/marketplace/plugins/clickup/lib/operations.json new file mode 100644 index 0000000000..fa2ef8b045 --- /dev/null +++ b/marketplace/plugins/clickup/lib/operations.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", + "title": "ClickUp datasource", + "description": "A schema defining ClickUp datasource", + "type": "api", + "defaults": {}, + "properties": { + "operation": { + "label": "", + "key": "clickup_operation", + "type": "react-component-api-endpoint", + "description": "Single select dropdown for operation", + "spec_url": "https://developer.clickup.com/openapi/673cf4cfdca96a0019533cad" + } + } +} \ No newline at end of file diff --git a/marketplace/plugins/clickup/lib/types.ts b/marketplace/plugins/clickup/lib/types.ts new file mode 100644 index 0000000000..00e976f831 --- /dev/null +++ b/marketplace/plugins/clickup/lib/types.ts @@ -0,0 +1,3 @@ +export type SourceOptions = { + apiKey: string; +}; diff --git a/marketplace/plugins/clickup/package.json b/marketplace/plugins/clickup/package.json new file mode 100644 index 0000000000..e46d35689d --- /dev/null +++ b/marketplace/plugins/clickup/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tooljet-marketplace/clickup", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "echo \"Error: run tests from root\" && exit 1", + "build": "ncc build lib/index.ts -o dist", + "watch": "ncc build lib/index.ts -o dist --watch" + }, + "homepage": "https://github.com/tooljet/tooljet#readme", + "dependencies": { + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "typescript": "^4.7.4", + "@vercel/ncc": "^0.34.0" + } +} diff --git a/marketplace/plugins/clickup/tsconfig.json b/marketplace/plugins/clickup/tsconfig.json new file mode 100644 index 0000000000..a18a801b14 --- /dev/null +++ b/marketplace/plugins/clickup/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib" + }, + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/marketplace/plugins/jira/lib/manifest.json b/marketplace/plugins/jira/lib/manifest.json index 4d9abdb583..210e6328f8 100644 --- a/marketplace/plugins/jira/lib/manifest.json +++ b/marketplace/plugins/jira/lib/manifest.json @@ -15,6 +15,9 @@ "options": { "url": { "type": "string" + }, + "personal_token": { + "encrypted": true } } }, @@ -57,11 +60,12 @@ "key": "personal_token", "type": "password", "description": "Enter your api token", - "hint": "You can generate a personal access token from your Jira account 'Manage account'." + "hint": "You can generate a personal access token from your Jira account 'Manage account'.", + "encrypted": true } } }, "required": [ "url" ] -} +} \ No newline at end of file diff --git a/marketplace/plugins/qdrant/lib/operations.json b/marketplace/plugins/qdrant/lib/operations.json index 8d7fc70627..d71458f821 100644 --- a/marketplace/plugins/qdrant/lib/operations.json +++ b/marketplace/plugins/qdrant/lib/operations.json @@ -146,7 +146,7 @@ "height": "36px" }, "withPayload": { - "label": "Include metadata", + "label": "Include payload", "key": "withPayload", "type": "codehinter", "description": "Whether to return payload values.", @@ -163,4 +163,4 @@ } } } -} \ No newline at end of file +} diff --git a/marketplace/plugins/supabase/lib/index.ts b/marketplace/plugins/supabase/lib/index.ts index e82952a2b1..875c7bfe9a 100644 --- a/marketplace/plugins/supabase/lib/index.ts +++ b/marketplace/plugins/supabase/lib/index.ts @@ -69,7 +69,16 @@ export default class Supabase implements QueryService { } if (error) { - throw new QueryError('Query could not be completed', error, {}); + const errorMessage = error?.message || "An unknown error occurred."; + let errorDetails: any = {}; + + const supabaseError = error as any; + const { code, hint } = supabaseError; + + errorDetails.code = code; + errorDetails.hint = hint; + + throw new QueryError('Query could not be completed', errorMessage, errorDetails); } return { diff --git a/marketplace/scripts/upload-to-s3.js b/marketplace/scripts/upload-to-s3.js index f6c281114a..6c490c972e 100644 --- a/marketplace/scripts/upload-to-s3.js +++ b/marketplace/scripts/upload-to-s3.js @@ -3,6 +3,7 @@ import readDir from 'recursive-readdir'; import { resolve as _resolve } from 'path'; import aws from 'aws-sdk'; import { lookup } from 'mime-types'; +import chalk from 'chalk'; const { config, S3 } = aws; const __dirname = _resolve(); @@ -30,7 +31,16 @@ const generateFileKey = (fileName) => { const s3 = new S3(); const uploadToS3 = async () => { + const start = Date.now(); + const errors = []; + let successCount = 0; + + console.log(chalk.cyanBright('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')); + console.log(chalk.cyanBright('šŸ“¤ S3 ASSETS UPLOADER')); + console.log(chalk.cyanBright('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')); + try { + console.log(`[${new Date().toLocaleTimeString()}] ℹ Scanning directory for files...`); const fileArray = await getDirectoryFilesRecursive(directoryPath, [ 'common', '.DS_Store', @@ -43,30 +53,76 @@ const uploadToS3 = async () => { 'tsconfig.json', ]); - fileArray.map((file) => { - // Configuring parameters for S3 Object - const S3params = { - Bucket: process.env.AWS_BUCKET, - Body: createReadStream(file), - Key: generateFileKey(file), - ContentType: lookup(file), - ContentEncoding: 'utf-8', - CacheControl: 'immutable,max-age=31536000,public', - }; - s3.upload(S3params, function (err, data) { - if (err) { - // Set the exit code while letting - // the process exit gracefully. - console.error(err); - process.exitCode = 1; - } else { - console.log(`Assets uploaded to S3: `, data); - } + console.log(`[${new Date().toLocaleTimeString()}] ℹ Found ${fileArray.length} files to upload`); + console.log(`[${new Date().toLocaleTimeString()}] ℹ Target bucket: ${process.env.AWS_BUCKET}\n`); + + const uploadPromises = fileArray.map((file, index) => { + return new Promise((resolve) => { + const S3params = { + Bucket: process.env.AWS_BUCKET, + Body: createReadStream(file), + Key: generateFileKey(file), + ContentType: lookup(file) || 'application/octet-stream', + ContentEncoding: 'utf-8', + CacheControl: 'immutable,max-age=31536000,public', + }; + + s3.upload(S3params, function (err, data) { + const indexStr = `[${(index + 1).toString().padStart(2, '0')}/${fileArray.length}]`; + if (err) { + console.log(chalk.redBright(`${indexStr} āŒ Failed to upload: ${file}`)); + console.error(chalk.gray(`↳ ${err.message}`)); + errors.push({ file, message: err.message }); + } else { + console.log(chalk.greenBright(`${indexStr} āœ… Uploaded: ${file}`)); + console.log( + chalk.gray( + JSON.stringify( + { + ETag: data.ETag, + Location: data.Location, + Key: data.Key, + Bucket: data.Bucket, + }, + null, + 2 + ) + ) + ); + successCount++; + } + resolve(); + }); }); }); + + await Promise.all(uploadPromises); + + const duration = ((Date.now() - start) / 1000).toFixed(1); + + console.log(chalk.cyanBright('\n━━━━━━━━━━━━━━━ UPLOAD SUMMARY ━━━━━━━━━━━━━━━━━')); + if (errors.length > 0) { + console.log(`[${new Date().toLocaleTimeString()}] āš ļø Upload completed with ${errors.length} error(s)`); + } else { + console.log(`[${new Date().toLocaleTimeString()}] šŸŽ‰ All files uploaded successfully`); + } + console.log(`[${new Date().toLocaleTimeString()}] āœ… Successfully uploaded: ${successCount}/${fileArray.length} files`); + console.log(`[${new Date().toLocaleTimeString()}] āŒ Failed uploads: ${errors.length}/${fileArray.length} files`); + console.log(`[${new Date().toLocaleTimeString()}] ℹ Total time: ${duration}s`); + + if (errors.length > 0) { + console.log(chalk.cyanBright('\n━━━━━━━━━━━━━━━ ERROR DETAILS ━━━━━━━━━━━━━━━━━')); + errors.forEach((err, idx) => { + console.log(chalk.red(`Error #${idx + 1}: ${err.file}`)); + console.log(chalk.gray(` ↳ ${err.message}`)); + }); + process.exitCode = 1; + } } catch (error) { + console.error(chalk.bgRed.white('āŒ Script failed with error:')); console.error(error); + process.exit(1); } }; -uploadToS3(); +uploadToS3(); \ No newline at end of file diff --git a/plugins/packages/firestore/lib/index.ts b/plugins/packages/firestore/lib/index.ts index 64d91c4f58..f8c39815bd 100644 --- a/plugins/packages/firestore/lib/index.ts +++ b/plugins/packages/firestore/lib/index.ts @@ -57,7 +57,18 @@ export default class FirestoreQueryService implements QueryService { break; } } catch (error) { - throw new QueryError('Query could not be completed', error.message, {}); + const errorMessage = error.message || "An unknown error occurred."; + let errorDetails: any = {}; + + if (error && error instanceof Error) { + const firestoreError = error as any; + const { code, name } = firestoreError; + + errorDetails.code = code as string; + errorDetails.name = name; + } + + throw new QueryError('Query could not be completed', errorMessage, errorDetails); } return { diff --git a/plugins/packages/grpc/lib/index.ts b/plugins/packages/grpc/lib/index.ts index 30aa514c22..a118aadfb8 100644 --- a/plugins/packages/grpc/lib/index.ts +++ b/plugins/packages/grpc/lib/index.ts @@ -46,8 +46,17 @@ export default class GRPC implements QueryService { metadata.add(sourceOptions.grpc_apikey_key, sourceOptions.grpc_apikey_value); } + let jsonMessage = {}; + if (queryOptions.jsonMessage) { + try { + jsonMessage = JSON.parse(queryOptions.jsonMessage); + } catch (e) { + throw new QueryError('Invalid JSON message', {}, {}); + } + } + const result = await new Promise((resolve, reject) => { - clientStub[rpc]({}, metadata, (err: any, response: any) => { + clientStub[rpc](jsonMessage, metadata, (err: any, response: any) => { if (err) { reject(err); } diff --git a/plugins/packages/grpc/lib/types.ts b/plugins/packages/grpc/lib/types.ts index 5b3865c609..d38528276d 100644 --- a/plugins/packages/grpc/lib/types.ts +++ b/plugins/packages/grpc/lib/types.ts @@ -11,5 +11,6 @@ export type SourceOptions = { export type QueryOptions = { operation: string; serviceName: string; + jsonMessage: string; rpc: string; }; diff --git a/plugins/packages/mariadb/lib/manifest.json b/plugins/packages/mariadb/lib/manifest.json index db1ff5afea..3d30bbda0a 100644 --- a/plugins/packages/mariadb/lib/manifest.json +++ b/plugins/packages/mariadb/lib/manifest.json @@ -19,7 +19,8 @@ "type": "string" }, "password": { - "type": "string" + "type": "string", + "encrypted": true }, "connectionLimit": { "type": "string" @@ -83,7 +84,8 @@ "label": "Password", "key": "password", "type": "password", - "description": "Enter password" + "description": "Enter password", + "encrypted": true }, "connectionLimit": { "label": "Connection Limit", diff --git a/plugins/packages/stripe/lib/operations.json b/plugins/packages/stripe/lib/operations.json index 8ec4bed410..4532254d99 100644 --- a/plugins/packages/stripe/lib/operations.json +++ b/plugins/packages/stripe/lib/operations.json @@ -1,16 +1,16 @@ { - "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", - "title": "Stripe datasource", - "description": "A schema defining stripe datasource", - "type": "api", - "defaults": {}, - "properties": { - "operation": { - "label": "", - "key": "stripe_operation", - "type": "react-component-api-endpoint", - "description": "Single select dropdown for operation", - "specUrl": "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json" - } + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", + "title": "Stripe datasource", + "description": "A schema defining stripe datasource", + "type": "api", + "defaults": {}, + "properties": { + "operation": { + "label": "", + "key": "stripe_operation", + "type": "react-component-api-endpoint", + "description": "Single select dropdown for operation", + "spec_url": "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json" } } +} \ No newline at end of file diff --git a/plugins/packages/woocommerce/.gitignore b/plugins/packages/woocommerce/.gitignore index cf1107688f..4c5f09d7c4 100644 --- a/plugins/packages/woocommerce/.gitignore +++ b/plugins/packages/woocommerce/.gitignore @@ -1,5 +1,4 @@ node_modules lib/*.d.* lib/*.js -lib/*.js.map -lib/operations.json \ No newline at end of file +lib/*.js.map \ No newline at end of file diff --git a/plugins/packages/woocommerce/lib/operations.json b/plugins/packages/woocommerce/lib/operations.json new file mode 100644 index 0000000000..e6041e3ee5 --- /dev/null +++ b/plugins/packages/woocommerce/lib/operations.json @@ -0,0 +1,105 @@ +{ + "title": "Woocommerce datasource", + "description": "A schema defining Woocommerce datasource", + "type": "api", + "defaults": {}, + "properties": { + "resource": { + "label": "Resource", + "key": "resource", + "className": "col-md-4", + "type": "dropdown-component-flip", + "description": "Resource select", + "list": [ + { "value": "product", "name": "Product" }, + { "value": "customer", "name": "Customer" }, + { "value": "order", "name": "Order" }, + { "value": "coupon", "name": "Coupon" } + ] + }, + "customer": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_customer", "name": "List all customers" }, + { "value": "update_customer", "name": "Update a customer" }, + { "value": "delete_customer", "name": "Delete a customer" }, + { "value": "batch_update_customer", "name": "Batch update customers" }, + { "value": "create_customer", "name": "Create a customer" }, + { "value": "retrieve_customer", "name": "Retrieve a customer" } + ] + }, + "list_customer": { + "page": { + "label": "Page", + "key": "page", + "type": "codehinter", + "description": "Enter page", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "", + "lineNumbers": false + }, + "context": { + "label": "Context", + "key": "context", + "type": "codehinter", + "description": "Enter context", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "", + "lineNumbers": false + } + } + }, + "product": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_product", "name": "List all products" }, + { "value": "update_product", "name": "Update a product" }, + { "value": "delete_product", "name": "Delete a product" }, + { "value": "batch_update_product", "name": "Batch update products" }, + { "value": "create_product", "name": "Create a product" }, + { "value": "retrieve_product", "name": "Retrieve a product" } + ] + } + }, + "order": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_order", "name": "List all orders" }, + { "value": "update_order", "name": "Update an order" }, + { "value": "delete_order", "name": "Delete an order" }, + { "value": "batch_update_order", "name": "Batch update orders" }, + { "value": "create_order", "name": "Create an order" }, + { "value": "retrieve_order", "name": "Retrieve an order" } + ] + } + }, + "coupon": { + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Single select dropdown for operation", + "list": [ + { "value": "list_coupon", "name": "List all coupons" }, + { "value": "create_coupon", "name": "Create a coupon" } + ] + } + } + } +} diff --git a/server/.version b/server/.version index 4eba2a62eb..f982feb41b 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.13.0 +3.14.0 diff --git a/server/migrations/1746520805456-AddResourceDataAudit.ts b/server/migrations/1746520805456-AddResourceDataAudit.ts new file mode 100644 index 0000000000..1950038c12 --- /dev/null +++ b/server/migrations/1746520805456-AddResourceDataAudit.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +export class AddResourceDataAudit1746520805456 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + 'audit_logs', + new TableColumn({ + name: 'resource_data', + type: 'json', + isNullable: true, + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/migrations/1747133448781-AddPluginIdUniqueConstraint.ts b/server/migrations/1747133448781-AddPluginIdUniqueConstraint.ts new file mode 100644 index 0000000000..01b0008006 --- /dev/null +++ b/server/migrations/1747133448781-AddPluginIdUniqueConstraint.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner, TableUnique } from "typeorm"; + +export class AddPluginIdUniqueConstraint1747133448781 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createUniqueConstraint( + "plugins", + new TableUnique({ + name: "UQ_plugin_pluginId", + columnNames: ["plugin_id"], + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropUniqueConstraint( + "plugins", + "UQ_plugin_pluginId" + ); + } + +} diff --git a/server/scripts/preview.sh b/server/scripts/preview.sh index 6664902874..d45e6ccb60 100644 --- a/server/scripts/preview.sh +++ b/server/scripts/preview.sh @@ -1,6 +1,8 @@ #!/bin/bash set -e +redis-server /etc/redis/redis.conf & + # Fix ownership and permissions chown -R postgres:postgres /var/lib/postgresql /var/run/postgresql chmod 0700 /var/lib/postgresql/13/main diff --git a/server/src/assets/marketplace/plugins.json b/server/src/assets/marketplace/plugins.json index fe24e4a6a7..b16d3727e9 100644 --- a/server/src/assets/marketplace/plugins.json +++ b/server/src/assets/marketplace/plugins.json @@ -221,5 +221,13 @@ "id": "azurerepos", "author": "Tooljet", "timestamp": "Mon, 23 Dec 2024 11:57:30 GMT" + }, + { + "name": "ClickUp", + "description": "ClickUp plugin for task, list, and doc management", + "version": "1.0.0", + "id": "clickup", + "author": "Tooljet", + "timestamp": "Wed, 16 Apr 2025 15:31:38 GMT" } ] \ No newline at end of file diff --git a/server/src/entities/audit_log.entity.ts b/server/src/entities/audit_log.entity.ts index 8a2b8a0806..49157d1ceb 100644 --- a/server/src/entities/audit_log.entity.ts +++ b/server/src/entities/audit_log.entity.ts @@ -23,6 +23,9 @@ export class AuditLog extends BaseEntity { @Column({ name: 'resource_type', type: 'enum', enum: MODULES }) resourceType: MODULES; + @Column('simple-json', { name: 'resource_data' }) + resourceData; + @Column({ name: 'action_type' }) actionType: string; diff --git a/server/src/entities/plugin.entity.ts b/server/src/entities/plugin.entity.ts index e69924f276..3047571f87 100644 --- a/server/src/entities/plugin.entity.ts +++ b/server/src/entities/plugin.entity.ts @@ -6,9 +6,11 @@ import { OneToOne, PrimaryGeneratedColumn, UpdateDateColumn, + Unique } from 'typeorm'; import { File } from 'src/entities/file.entity'; +@Unique(['pluginId']) @Entity({ name: 'plugins' }) export class Plugin { @PrimaryGeneratedColumn() diff --git a/server/src/modules/app/constants/modules.ts b/server/src/modules/app/constants/modules.ts index ab87064a84..1522332a09 100644 --- a/server/src/modules/app/constants/modules.ts +++ b/server/src/modules/app/constants/modules.ts @@ -39,4 +39,5 @@ export enum MODULES { APP_PERMISSIONS = 'AppPermissions', AUDIT_LOGS = 'auditLogs', EXTERNAL_APIS = 'externalApis', + MODULES = 'Modules', } diff --git a/server/src/modules/app/module.ts b/server/src/modules/app/module.ts index 435122b0cb..7f95f46b46 100644 --- a/server/src/modules/app/module.ts +++ b/server/src/modules/app/module.ts @@ -39,6 +39,7 @@ import { TemplatesModule } from '@modules/templates/module'; import { ImportExportResourcesModule } from '@modules/import-export-resources/module'; import { TooljetDbModule } from '@modules/tooljet-db/module'; import { WorkflowsModule } from '@modules/workflows/module'; +import { ModulesModule } from '@modules/modules/module'; import { AiModule } from '@modules/ai/module'; import { CustomStylesModule } from '@modules/custom-styles/module'; import { AppPermissionsModule } from '@modules/app-permissions/module'; @@ -95,6 +96,7 @@ export class AppModule implements OnModuleInit { await TemplatesModule.register(configs), await TooljetDbModule.register(configs), await WorkflowsModule.register(configs), + await ModulesModule.register(configs), await AiModule.register(configs), await CustomStylesModule.register(configs), await AppPermissionsModule.register(configs), diff --git a/server/src/modules/apps/constants/index.ts b/server/src/modules/apps/constants/index.ts index 234950ca4d..81b7b8cae4 100644 --- a/server/src/modules/apps/constants/index.ts +++ b/server/src/modules/apps/constants/index.ts @@ -15,6 +15,7 @@ export enum FEATURE_KEY { export enum APP_TYPES { FRONT_END = 'front-end', WORKFLOW = 'workflow', + MODULE = 'module', } export enum LayoutDimensionUnits { diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index 27fd03c034..7d3b865b60 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -31,6 +31,7 @@ import { FoldersUtilService } from '@modules/folders/util.service'; import { FolderAppsUtilService } from '@modules/folder-apps/util.service'; import { PageService } from './services/page.service'; import { EventsService } from './services/event.service'; +import { ComponentsService } from './services/component.service'; import { LICENSE_FIELD } from '@modules/licensing/constants'; import { AppEnvironment } from '@entities/app_environments.entity'; import { OrganizationThemesUtilService } from '@modules/organization-themes/util.service'; @@ -38,6 +39,7 @@ import { IAppsService } from './interfaces/IService'; import { AiUtilService } from '@modules/ai/util.service'; import { RequestContext } from '@modules/request-context/service'; import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; +import { MODULES } from '@modules/app/constants/modules'; @Injectable() export class AppsService implements IAppsService { @@ -52,8 +54,9 @@ export class AppsService implements IAppsService { protected readonly pageService: PageService, protected readonly eventService: EventsService, protected readonly organizationThemeUtilService: OrganizationThemesUtilService, - protected readonly aiUtilService: AiUtilService - ) { } + protected readonly aiUtilService: AiUtilService, + protected readonly componentsService: ComponentsService + ) {} async create(user: User, appCreateDto: AppCreateDto) { const { name, icon, type } = appCreateDto; return await dbTransactionWrap(async (manager: EntityManager) => { @@ -98,8 +101,8 @@ export class AppsService implements IAppsService { const version = versionId ? await this.versionRepository.findById(versionId, app.id) : versionName - ? await this.versionRepository.findByName(versionName, app.id) - : // Handle version retrieval based on env + ? await this.versionRepository.findByName(versionName, app.id) + : // Handle version retrieval based on env await this.versionRepository.findLatestVersionForEnvironment( app.id, envId, @@ -200,6 +203,13 @@ export class AppsService implements IAppsService { apps = await this.appsUtilService.all(user, parseInt(page || '1'), searchKey, type); } + if (type === 'module') { + for (const app of apps) { + const appVersionId = app?.appVersions[0]?.id; + app.moduleContainer = await this.pageService.findModuleContainer(appVersionId); + } + } + const totalCount = await this.appsUtilService.count(user, searchKey, type); const totalPageCount = folderId ? totalFolderCount : totalCount; @@ -296,42 +306,53 @@ export class AppsService implements IAppsService { } async getBySlug(app: App, user: User): Promise { - const versionToLoad = app.currentVersionId - ? await this.versionRepository.findVersion(app.currentVersionId) - : await this.versionRepository.findVersion(app.editingVersion?.id); + const prepareResponse = async (app) => { + const versionToLoad = app.currentVersionId + ? await this.versionRepository.findVersion(app.currentVersionId) + : await this.versionRepository.findVersion(app.editingVersion?.id); - const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : []; - const eventsForVersion = app.editingVersion ? await this.eventService.findEventsForVersion(versionToLoad.id) : []; - const appTheme = await this.organizationThemeUtilService.getTheme( - app.organizationId, - versionToLoad?.globalSettings?.theme?.id - ); + const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : []; + const eventsForVersion = app.editingVersion ? await this.eventService.findEventsForVersion(versionToLoad.id) : []; + const appTheme = await this.organizationThemeUtilService.getTheme( + app.organizationId, + versionToLoad?.globalSettings?.theme?.id + ); - if (app?.isPublic && user) { - RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { - userId: user.id, - organizationId: user.organizationId, - resourceId: app.id, - resourceName: app.name, - }); - } + if (app?.isPublic && user) { + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { + userId: user.id, + organizationId: user.organizationId, + resourceId: app.id, + resourceName: app.name, + resourceType: MODULES.APP, + }); + } - // serialize - return { - current_version_id: app['currentVersionId'], - data_queries: versionToLoad?.dataQueries, - definition: versionToLoad?.definition, - is_public: app.isPublic, - is_maintenance_on: app.isMaintenanceOn, - name: app.name, - slug: app.slug, - events: eventsForVersion, - pages: this.appsUtilService.mergeDefaultComponentData(pagesForVersion), - homePageId: versionToLoad.homePageId, - globalSettings: { ...versionToLoad.globalSettings, theme: appTheme }, - showViewerNavigation: versionToLoad.showViewerNavigation, - pageSettings: versionToLoad?.pageSettings, + // serialize + return { + current_version_id: app['currentVersionId'], + data_queries: versionToLoad?.dataQueries, + definition: versionToLoad?.definition, + is_public: app.isPublic, + is_maintenance_on: app.isMaintenanceOn, + name: app.name, + slug: app.slug, + events: eventsForVersion, + pages: this.appsUtilService.mergeDefaultComponentData(pagesForVersion), + homePageId: versionToLoad.homePageId, + globalSettings: { ...versionToLoad.globalSettings, theme: appTheme }, + showViewerNavigation: versionToLoad.showViewerNavigation, + pageSettings: versionToLoad?.pageSettings, + }; }; + + const response = await prepareResponse(app); + + const modules = await this.appsUtilService.fetchModules(app, false, undefined); + + response['modules'] = await Promise.all(modules.map((module) => prepareResponse(module))); + + return response; } async release(app: App, user: User, versionReleaseDto: VersionReleaseDto) { diff --git a/server/src/modules/apps/services/component.service.ts b/server/src/modules/apps/services/component.service.ts index fcc01e52f0..c6ef31f5b8 100644 --- a/server/src/modules/apps/services/component.service.ts +++ b/server/src/modules/apps/services/component.service.ts @@ -12,7 +12,7 @@ const _ = require('lodash'); @Injectable() export class ComponentsService implements IComponentsService { - constructor(protected eventHandlerService: EventsService) {} + constructor(protected eventHandlerService: EventsService) { } findOne(id: string): Promise { return dbTransactionWrap((manager: EntityManager) => { @@ -97,6 +97,7 @@ export class ComponentsService implements IComponentsService { } else if ( (componentData.type === 'DropdownV2' || componentData.type === 'MultiselectV2' || + componentData.type === 'ModuleContainer' || componentData.type === 'Steps') && _.isArray(objValue) ) { diff --git a/server/src/modules/apps/services/page.service.ts b/server/src/modules/apps/services/page.service.ts index fa0b2864e0..02bea48dab 100644 --- a/server/src/modules/apps/services/page.service.ts +++ b/server/src/modules/apps/services/page.service.ts @@ -304,4 +304,8 @@ export class PageService implements IPageService { return await this.pageHelperService.rearrangePagesOrderPostDeletion(pageExists, manager); }, appVersionId); } + + async findModuleContainer(appVersionId: string): Promise { + return this.pageHelperService.findModuleContainer(appVersionId); + } } diff --git a/server/src/modules/apps/services/page.util.service.ts b/server/src/modules/apps/services/page.util.service.ts index cb10863972..aface2cc16 100644 --- a/server/src/modules/apps/services/page.util.service.ts +++ b/server/src/modules/apps/services/page.util.service.ts @@ -79,4 +79,8 @@ export class PageHelperService implements IPageHelperService { page.index = dto.index; return page; } + + public async findModuleContainer(appVersionId: string): Promise { + return null; + } } diff --git a/server/src/modules/apps/services/widget-config/index.js b/server/src/modules/apps/services/widget-config/index.js index fdb4cf26df..c1ac0d205b 100644 --- a/server/src/modules/apps/services/widget-config/index.js +++ b/server/src/modules/apps/services/widget-config/index.js @@ -58,6 +58,8 @@ import { kanbanBoardConfig } from './kanbanBoard'; import { datetimePickerV2Config } from './datetimepickerV2'; import { datePickerV2Config } from './datepickerV2'; import { timePickerConfig } from './timepicker'; +import { moduleContainerConfig } from './moduleContainer'; +import { moduleViewerConfig } from './moduleViewer'; import { emailinputConfig } from './emailinput'; import { phoneinputConfig } from './phoneinput'; import {currencyinputConfig} from './currencyinput'; @@ -126,6 +128,8 @@ const widgets = { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig }; const universalProps = { diff --git a/server/src/modules/apps/services/widget-config/moduleContainer.js b/server/src/modules/apps/services/widget-config/moduleContainer.js new file mode 100644 index 0000000000..af0f77c823 --- /dev/null +++ b/server/src/modules/apps/services/widget-config/moduleContainer.js @@ -0,0 +1,36 @@ +export const moduleContainerConfig = { + name: 'ModuleContainer', + displayName: 'Module Container', + description: 'Module Container', + component: 'ModuleContainer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + inputItems: { type: 'array', displayName: 'Input' }, + outputItems: { type: 'array', displayName: 'Output' }, + }, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + inputItems: { value: [] }, + outputItems: { value: [] }, + }, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, +}; diff --git a/server/src/modules/apps/services/widget-config/moduleViewer.js b/server/src/modules/apps/services/widget-config/moduleViewer.js new file mode 100644 index 0000000000..b0f5342787 --- /dev/null +++ b/server/src/modules/apps/services/widget-config/moduleViewer.js @@ -0,0 +1,31 @@ +export const moduleViewerConfig = { + name: 'ModuleViewer', + displayName: 'Module', + description: 'Module', + component: 'ModuleViewer', + defaultSize: { + width: 10, + height: 400, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: {}, + events: {}, + styles: {}, + exposedVariables: {}, + actions: [], + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: {}, + events: [], + styles: { + backgroundColor: { value: '#fff' }, + }, + }, + }; + \ No newline at end of file diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 3db7df4a99..ff8aaeda48 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -37,6 +37,8 @@ import { DataSourcesRepository } from '@modules/data-sources/repository'; import { IAppsUtilService } from './interfaces/IUtilService'; import { DataSourcesUtilService } from '@modules/data-sources/util.service'; import { AppVersionUpdateDto } from '@dto/app-version-update.dto'; +import { Component } from 'src/entities/component.entity'; +import { Layout } from 'src/entities/layout.entity'; import { WorkspaceAppsResponseDto } from '@modules/external-apis/dto'; import { DataQuery } from '@entities/data_query.entity'; import { DataSource } from '@entities/data_source.entity'; @@ -52,7 +54,7 @@ export class AppsUtilService implements IAppsUtilService { protected readonly abilityService: AbilityService, protected readonly dataSourceRepository: DataSourcesRepository, protected readonly dataSourceUtilService: DataSourcesUtilService - ) {} + ) { } async create(name: string, user: User, type: string, manager: EntityManager): Promise { return await dbTransactionWrap(async (manager: EntityManager) => { const app = await catchDbException(() => { @@ -92,8 +94,52 @@ export class AppsUtilService implements IAppsUtilService { }) ); + if (type === 'module') { + const moduleContainer = await manager.save( + manager.create(Component, { + name: 'ModuleContainer', + type: 'ModuleContainer', + pageId: defaultHomePage.id, + properties: { + inputItems: { value: [] }, + outputItems: { value: [] }, + visibility: { value: '{{true}}' }, + }, + styles: { + backgroundColor: { value: '#fff' }, + }, + displayPreferences: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{true}}' }, + }, + }) + ); + + await manager.save( + manager.create(Layout, { + component: moduleContainer, + type: 'desktop', + top: 50, + left: 6, + height: 400, + width: 38, + }) + ); + + await manager.save( + manager.create(Layout, { + component: moduleContainer, + type: 'mobile', + top: 50, + left: 6, + height: 400, + width: 38, + }) + ); + } + // Set default values for app version - appVersion.showViewerNavigation = true; + appVersion.showViewerNavigation = type === 'module' ? false : true; appVersion.homePageId = defaultHomePage.id; appVersion.globalSettings = { hideHeader: false, @@ -179,8 +225,8 @@ export class AppsUtilService implements IAppsUtilService { const processEnvironmentName = environmentName ? environmentName : !isMultiEnvironmentEnabled - ? 'development' - : null; + ? 'development' + : null; const environment: AppEnvironment = environmentId ? await this.appEnvironmentUtilService.get(organizationId, environmentId) @@ -396,20 +442,24 @@ export class AppsUtilService implements IAppsUtilService { const viewableApps = userAppPermissions.hideAll ? [null, ...userAppPermissions.editableAppsId] : [ - null, - ...Array.from( - new Set([ - ...userAppPermissions.editableAppsId, - ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)), - ]) - ), - ]; + null, + ...Array.from( + new Set([ + ...userAppPermissions.editableAppsId, + ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)), + ]) + ), + ]; const viewableAppsQb = manager .createQueryBuilder(AppBase, 'viewable_apps') .innerJoin('viewable_apps.user', 'user') .addSelect(['user.firstName', 'user.lastName']) .where('viewable_apps.organizationId = :organizationId', { organizationId: user.organizationId }); + if (type === 'module') { + viewableAppsQb.leftJoinAndSelect('viewable_apps.appVersions', 'versions'); + } + if (type) viewableAppsQb.andWhere('viewable_apps.type = :type', { type: type }); if (searchKey) { @@ -526,6 +576,43 @@ export class AppsUtilService implements IAppsUtilService { return components; } + async fetchModules(app: App, allVersions: boolean = false, versionId: string): Promise { + const versionToLoad = versionId + ? await this.versionRepository.findVersion(versionId) + : app.currentVersionId + ? await this.versionRepository.findVersion(app.currentVersionId) + : await this.versionRepository.findVersion(app.editingVersion?.id); + + const modules = await dbTransactionWrap(async (manager) => { + const moduleComponents = await manager + .createQueryBuilder(Component, 'component') + .leftJoinAndSelect(Page, 'page', 'page.id = component.page_id') + .leftJoinAndSelect(AppVersion, 'app_version', 'app_version.id = page.app_version_id') + .leftJoinAndSelect(App, 'app', 'app.id = app_version.app_id') + .andWhere( + `component.type = :module ${allVersions ? '' : 'AND app_version.id = :appVersionId'} AND app.id = :appId`, + { + module: 'ModuleViewer', + appVersionId: versionToLoad.id, + appId: app.id, + } + ) + .getMany(); + + const moduleAppIds = moduleComponents.map((moduleComponent) => moduleComponent.properties.moduleAppId.value); + + const modules = + moduleAppIds.length > 0 + ? await manager + .createQueryBuilder(App, 'app') + .where('app.id IN (:...moduleAppIds)', { moduleAppIds }) + .distinct(true) + .getMany() + : []; + return modules; + }); + return modules; + } async findAllOrganizationApps(organizationId: string): Promise { return await this.appRepository.findAllOrganizationApps(organizationId); } diff --git a/server/src/modules/audit-logs/interfaces/IService.ts b/server/src/modules/audit-logs/interfaces/IService.ts index aff26abaed..80702b8fdb 100644 --- a/server/src/modules/audit-logs/interfaces/IService.ts +++ b/server/src/modules/audit-logs/interfaces/IService.ts @@ -7,6 +7,6 @@ export interface IAuditLogService { perform( { userId, organizationId, resourceId, resourceType, actionType, resourceName, metadata }: AuditLogFields, manager?: EntityManager - ): Promise; + ): Promise; findPerPage(user: User, query: AuditLogsQuery): Promise; } diff --git a/server/src/modules/audit-logs/types/index.ts b/server/src/modules/audit-logs/types/index.ts index 1bde3d3ec9..6d234fd18f 100644 --- a/server/src/modules/audit-logs/types/index.ts +++ b/server/src/modules/audit-logs/types/index.ts @@ -18,10 +18,12 @@ export interface AuditLogFields { organizationId: string; resourceId: string; resourceType: MODULES; + resourceData?: object; actionType: string; resourceName?: string; ipAddress?: string; metadata?: object; + organizationIds?: Array; } export interface Features { diff --git a/server/src/modules/auth/constants/feature.ts b/server/src/modules/auth/constants/feature.ts index b0f5bcf7c9..c21c4195ea 100644 --- a/server/src/modules/auth/constants/feature.ts +++ b/server/src/modules/auth/constants/feature.ts @@ -28,12 +28,15 @@ export const FEATURES: FeaturesConfig = { }, [FEATURE_KEY.FORGOT_PASSWORD]: { isPublic: true, + auditLogsKey: 'USER_PASSWORD_FORGOT', }, [FEATURE_KEY.RESET_PASSWORD]: { isPublic: true, + auditLogsKey: 'USER_PASSWORD_RESET', }, [FEATURE_KEY.OAUTH_SIGN_IN]: { isPublic: true, + auditLogsKey: 'USER_LOGIN', }, [FEATURE_KEY.OAUTH_OPENID_CONFIGS]: { isPublic: true, @@ -43,6 +46,7 @@ export const FEATURES: FeaturesConfig = { }, [FEATURE_KEY.OAUTH_COMMON_SIGN_IN]: { isPublic: true, + auditLogsKey: 'USER_LOGIN', }, [FEATURE_KEY.OAUTH_SAML_RESPONSE]: { isPublic: true, diff --git a/server/src/modules/auth/service.ts b/server/src/modules/auth/service.ts index 7c9fb3ad89..cfea254fe0 100644 --- a/server/src/modules/auth/service.ts +++ b/server/src/modules/auth/service.ts @@ -124,6 +124,9 @@ export class AuthService implements IAuthService { organizationId: organization.id, resourceId: user.id, resourceName: user.email, + resourceData: { + auth_method: 'password', + }, }); } @@ -184,6 +187,13 @@ export class AuthService implements IAuthService { forgotPasswordToken: null, passwordRetryCount: 0, }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); } } @@ -195,6 +205,13 @@ export class AuthService implements IAuthService { } const forgotPasswordToken = uuid.v4(); await this.userRepository.updateOne(user.id, { forgotPasswordToken }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); this.eventEmitter.emit('emailEvent', { type: EMAIL_EVENTS.SEND_PASSWORD_RESET_EMAIL, payload: { diff --git a/server/src/modules/data-sources/ability/index.ts b/server/src/modules/data-sources/ability/index.ts index 60686e24d7..5a205ece7e 100644 --- a/server/src/modules/data-sources/ability/index.ts +++ b/server/src/modules/data-sources/ability/index.ts @@ -5,6 +5,7 @@ import { UserAllPermissions } from '@modules/app/types'; import { FEATURE_KEY } from '../constants'; import { DataSource } from '@entities/data_source.entity'; import { MODULES } from '@modules/app/constants/modules'; +import { getTooljetEdition } from '@helpers/utils.helper'; type Subjects = InferSubjects | 'all'; export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>; @@ -33,10 +34,13 @@ export class FeatureAbilityFactory extends AbilityFactory const isAllViewable = !!resourcePermissions?.isAllUsable; const dataSourceId = request?.tj_resource_id; - + const toolJetEdition = getTooljetEdition(); // Oauth end points available to all can(FEATURE_KEY.GET_OAUTH2_BASE_URL, DataSource); can(FEATURE_KEY.AUTHORIZE, DataSource); + if ((toolJetEdition == 'ee' && superAdmin) || (toolJetEdition !== 'ee' && isAdmin)) { + can(FEATURE_KEY.QUERIES_DATASOURCE_LINKED_TO_MARKETPLACE_PLUGIN, DataSource); + } if (isBuilder) { // Only builder can do scope change, Get call is there on app builder @@ -56,6 +60,7 @@ export class FeatureAbilityFactory extends AbilityFactory FEATURE_KEY.TEST_CONNECTION, FEATURE_KEY.SCOPE_CHANGE, FEATURE_KEY.GET_FOR_APP, + FEATURE_KEY.QUERIES_LINKED_TO_DATASOURCE, ], DataSource ); @@ -70,7 +75,7 @@ export class FeatureAbilityFactory extends AbilityFactory ); if (isCanDelete) { - can(FEATURE_KEY.DELETE, DataSource); + can([FEATURE_KEY.DELETE, FEATURE_KEY.QUERIES_LINKED_TO_DATASOURCE], DataSource); } if (isCanCreate) { can(FEATURE_KEY.CREATE, DataSource); diff --git a/server/src/modules/data-sources/constants/feature.ts b/server/src/modules/data-sources/constants/feature.ts index 60f6d585d6..78ded94e26 100644 --- a/server/src/modules/data-sources/constants/feature.ts +++ b/server/src/modules/data-sources/constants/feature.ts @@ -20,5 +20,7 @@ export const FEATURES: FeaturesConfig = { [FEATURE_KEY.GET_OAUTH2_BASE_URL]: {}, [FEATURE_KEY.AUTHORIZE]: {}, [FEATURE_KEY.GET_FOR_APP]: {}, + [FEATURE_KEY.QUERIES_LINKED_TO_DATASOURCE]: {}, + [FEATURE_KEY.QUERIES_DATASOURCE_LINKED_TO_MARKETPLACE_PLUGIN]: {}, }, }; diff --git a/server/src/modules/data-sources/constants/index.ts b/server/src/modules/data-sources/constants/index.ts index 7a76daf665..ac09f78c3c 100644 --- a/server/src/modules/data-sources/constants/index.ts +++ b/server/src/modules/data-sources/constants/index.ts @@ -9,6 +9,8 @@ export enum FEATURE_KEY { TEST_CONNECTION = 'TEST_CONNECTION', GET_OAUTH2_BASE_URL = 'GET_OAUTH2_BASE_URL', AUTHORIZE = 'AUTHORIZE', + QUERIES_LINKED_TO_DATASOURCE = 'QUERIES_LINKED_TO_DATASOURCE', + QUERIES_DATASOURCE_LINKED_TO_MARKETPLACE_PLUGIN = 'QUERIES_DATASOURCE_LINKED_TO_MARKETPLACE_PLUGIN', } export enum DataSourceTypes { diff --git a/server/src/modules/data-sources/controller.ts b/server/src/modules/data-sources/controller.ts index a0b1845a3d..51d1df0af6 100644 --- a/server/src/modules/data-sources/controller.ts +++ b/server/src/modules/data-sources/controller.ts @@ -111,7 +111,7 @@ export class DataSourcesController implements IDataSourcesController { @InitFeature(FEATURE_KEY.GET_OAUTH2_BASE_URL) @UseGuards(FeatureAbilityGuard) - @Get('fetch-oauth2-base-url') + @Post('fetch-oauth2-base-url') getAuthUrl(@Body() getDataSourceOauthUrlDto: GetDataSourceOauthUrlDto) { return this.dataSourcesService.getAuthUrl(getDataSourceOauthUrlDto); } @@ -129,6 +129,20 @@ export class DataSourcesController implements IDataSourcesController { return; } + @InitFeature(FEATURE_KEY.QUERIES_DATASOURCE_LINKED_TO_MARKETPLACE_PLUGIN) + @UseGuards(FeatureAbilityGuard) + @Get('dependent-queries/marketplace-plugin/:plugin_id') + async findDatasourcesAndQueriesOfMarketplacePlugin(@User() user: UserEntity, @Param('plugin_id') pluginId) { + return await this.dataSourcesService.findDatasourcesAndQueriesOfMarketplacePlugin(pluginId); + } + + @InitFeature(FEATURE_KEY.QUERIES_LINKED_TO_DATASOURCE) + @UseGuards(FeatureAbilityGuard) + @Get('dependent-queries/:datasource_id') + async findQueriesLinkedToDatasource(@User() user: UserEntity, @Param('datasource_id') datasourceId: string) { + return await this.dataSourcesService.findQueriesLinkedToDatasource(datasourceId); + } + @InitFeature(FEATURE_KEY.AUTHORIZE) @UseGuards(FeatureAbilityGuard) @Post('decrypt') diff --git a/server/src/modules/data-sources/module.ts b/server/src/modules/data-sources/module.ts index f914bbcd2e..c8d9e51b50 100644 --- a/server/src/modules/data-sources/module.ts +++ b/server/src/modules/data-sources/module.ts @@ -10,6 +10,7 @@ import { InstanceSettingsModule } from '@modules/instance-settings/module'; import { VersionRepository } from '@modules/versions/repository'; import { AppsRepository } from '@modules/apps/repository'; import { TooljetDbModule } from '@modules/tooljet-db/module'; +import { OrganizationRepository } from '@modules/organizations/repository'; import { SessionModule } from '@modules/session/module'; import { SampleDBScheduler } from './schedulers/sample-db.scheduler'; @@ -21,6 +22,7 @@ export class DataSourcesModule { const { DataSourcesUtilService } = await import(`${importPath}/data-sources/util.service`); const { PluginsServiceSelector } = await import(`${importPath}/data-sources/services/plugin-selector.service`); const { SampleDataSourceService } = await import(`${importPath}/data-sources/services/sample-ds.service`); + const { OrganizationsService } = await import(`${importPath}/organizations/service`); return { module: DataSourcesModule, @@ -42,6 +44,8 @@ export class DataSourcesModule { PluginsRepository, SampleDataSourceService, FeatureAbilityFactory, + OrganizationsService, + OrganizationRepository, SampleDBScheduler, ], controllers: [DataSourcesController], diff --git a/server/src/modules/data-sources/repository.ts b/server/src/modules/data-sources/repository.ts index e3cb9a0fb5..0bc8195d47 100644 --- a/server/src/modules/data-sources/repository.ts +++ b/server/src/modules/data-sources/repository.ts @@ -168,4 +168,26 @@ export class DataSourcesRepository extends Repository { }); }, manager || this.manager); } + + getDatasourceByPluginId(pluginId: string) { + return dbTransactionWrap((manager: EntityManager) => { + return manager.find(DataSource, { + where: { + pluginId: pluginId, + }, + relations: ['dataQueries'], + }); + }); + } + + getQueriesByDatasourceId(datasourceId) { + return dbTransactionWrap((manager: EntityManager) => { + return manager.find(DataSource, { + where: { + id: datasourceId, + }, + relations: ['dataQueries'], + }); + }); + } } diff --git a/server/src/modules/data-sources/service.ts b/server/src/modules/data-sources/service.ts index ae10f903a5..f104f228f9 100644 --- a/server/src/modules/data-sources/service.ts +++ b/server/src/modules/data-sources/service.ts @@ -20,6 +20,8 @@ import { GetQueryVariables, UpdateOptions } from './types'; import { DataSource } from '@entities/data_source.entity'; import { PluginsServiceSelector } from './services/plugin-selector.service'; import { IDataSourcesService } from './interfaces/IService'; +// import { FEATURE_KEY } from './constants'; +import { OrganizationsService } from '@modules/organizations/service'; import { RequestContext } from '@modules/request-context/service'; import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; @@ -30,7 +32,8 @@ export class DataSourcesService implements IDataSourcesService { protected readonly dataSourcesUtilService: DataSourcesUtilService, protected readonly abilityService: AbilityService, protected readonly appEnvironmentsUtilService: AppEnvironmentUtilService, - protected readonly pluginsServiceSelector: PluginsServiceSelector + protected readonly pluginsServiceSelector: PluginsServiceSelector, + protected readonly organizationsService: OrganizationsService ) {} async getForApp(query: GetQueryVariables, user: User): Promise<{ data_sources: object[] }> { @@ -43,7 +46,6 @@ export class DataSourcesService implements IDataSourcesService { const dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, query ?? {}); let staticDataSources = await this.dataSourcesRepository.getAllStaticDataSources(query.appVersionId); - if (!shouldIncludeWorkflows) { // remove workflowsdefault data source from static data sources staticDataSources = staticDataSources.filter((dataSource) => dataSource.kind !== 'workflows'); @@ -176,6 +178,12 @@ export class DataSourcesService implements IDataSourcesService { if (dataSource.type === DataSourceTypes.SAMPLE) { throw new BadRequestException('Cannot delete sample data source'); } + + const result = await this.findQueriesLinkedToDatasource(dataSourceId); + if (result.dependent_queries) { + throw new BadRequestException(`Datasource can't be deleted, queries are in use`); + } + await this.dataSourcesRepository.delete(dataSourceId); // Setting data for audit logs @@ -243,4 +251,30 @@ export class DataSourcesService implements IDataSourcesService { await this.dataSourcesUtilService.authorizeOauth2(dataSource, code, user.id, environmentId, user.organizationId); return; } + + async findQueriesLinkedToDatasource(datasourceId: string) { + const dataSourceDetails = await this.dataSourcesRepository.getQueriesByDatasourceId(datasourceId); + if (dataSourceDetails.length == 0) return { datasources: 0, dependent_queries: 0 }; + + const queries = []; + dataSourceDetails.forEach((datasourceDetail) => { + const { dataQueries = [] } = datasourceDetail; + if (dataQueries.length) queries.push(...dataQueries); + }); + + return { datasources: dataSourceDetails.length, dependent_queries: queries.length }; + } + + async findDatasourcesAndQueriesOfMarketplacePlugin(pluginId: string) { + const dataSourcesByMarketplacePlugin = await this.dataSourcesRepository.getDatasourceByPluginId(pluginId); + if (!dataSourcesByMarketplacePlugin.length) return { dependent_queries: 0 }; + + const queries = []; + dataSourcesByMarketplacePlugin?.forEach((datasource) => { + if (datasource.dataQueries.length) queries.push(...datasource.dataQueries); + }); + return { + dependent_queries: queries.length, + }; + } } diff --git a/server/src/modules/data-sources/types/index.ts b/server/src/modules/data-sources/types/index.ts index 22c56c3e31..791a5b8af0 100644 --- a/server/src/modules/data-sources/types/index.ts +++ b/server/src/modules/data-sources/types/index.ts @@ -14,6 +14,8 @@ interface Features { [FEATURE_KEY.GET_OAUTH2_BASE_URL]: FeatureConfig; [FEATURE_KEY.AUTHORIZE]: FeatureConfig; [FEATURE_KEY.GET_FOR_APP]: FeatureConfig; + [FEATURE_KEY.QUERIES_LINKED_TO_DATASOURCE]: FeatureConfig; + [FEATURE_KEY.QUERIES_DATASOURCE_LINKED_TO_MARKETPLACE_PLUGIN]: FeatureConfig; } export interface FeaturesConfig { diff --git a/server/src/modules/data-sources/util.service.ts b/server/src/modules/data-sources/util.service.ts index 961f817064..03ad5ddd52 100644 --- a/server/src/modules/data-sources/util.service.ts +++ b/server/src/modules/data-sources/util.service.ts @@ -189,17 +189,16 @@ export class DataSourcesUtilService implements IDataSourcesUtilService { /* Basic plan customer. lets update all environment options. this will help us to run the queries successfully when the user buys enterprise plan - */ - await Promise.all( - allEnvs.map(async (envToUpdate) => { - dataSource.options = ( - await this.appEnvironmentUtilService.getOptions(dataSourceId, organizationId, envToUpdate.id) - ).options; + */ - const newOptions = await this.parseOptionsForUpdate(dataSource, options, manager); - await this.appEnvironmentUtilService.updateOptions(newOptions, envToUpdate.id, dataSource.id, manager); - }) - ); + const newOptions = await this.parseOptionsForUpdate(dataSource, options, manager); + for (const env of allEnvs) { + dataSource.options = ( + await this.appEnvironmentUtilService.getOptions(dataSourceId, organizationId, env.id) + ).options; + + await this.appEnvironmentUtilService.updateOptions(newOptions, env.id, dataSource.id, manager); + } } const updatableParams = { id: dataSourceId, diff --git a/server/src/modules/modules/IModulesController.ts b/server/src/modules/modules/IModulesController.ts new file mode 100644 index 0000000000..cd84b9c534 --- /dev/null +++ b/server/src/modules/modules/IModulesController.ts @@ -0,0 +1,6 @@ +import { User } from '@entities/user.entity'; +import { AppCreateDto } from '@modules/apps/dto'; + +export interface IModulesController { + create(user: User, appCreateDto: AppCreateDto): Promise; +} diff --git a/server/src/modules/modules/constants/index.ts b/server/src/modules/modules/constants/index.ts new file mode 100644 index 0000000000..4adfa4a519 --- /dev/null +++ b/server/src/modules/modules/constants/index.ts @@ -0,0 +1,4 @@ +export enum FEATURE_KEY { + CREATE_MODULE = 'create_module', + GET_MODULES = 'get_modules', +} diff --git a/server/src/modules/modules/module.ts b/server/src/modules/modules/module.ts new file mode 100644 index 0000000000..a7af218b81 --- /dev/null +++ b/server/src/modules/modules/module.ts @@ -0,0 +1,54 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { getImportPath } from '@modules/app/constants'; +import { ThemesModule } from '@modules/organization-themes/module'; +import { FoldersModule } from '@modules/folders/module'; +import { FolderAppsModule } from '@modules/folder-apps/module'; +import { OrganizationsModule } from '@modules/organizations/module'; +import { AppEnvironmentsModule } from '@modules/app-environments/module'; +import { OrganizationRepository } from '@modules/organizations/repository'; +import { DataSourcesRepository } from '@modules/data-sources/repository'; +import { VersionRepository } from '@modules/versions/repository'; +import { DataSourcesModule } from '@modules/data-sources/module'; +import { AiModule } from '@modules/ai/module'; +import { AppsRepository } from '@modules/apps/repository'; +import { AppPermissionsModule } from '@modules/app-permissions/module'; +@Module({}) +export class ModulesModule { + static async register(configs: { IS_GET_CONTEXT: boolean }): Promise { + const importPath = await getImportPath(configs.IS_GET_CONTEXT); + const { ModulesController } = await import(`${importPath}/modules/modules.controller`); + const { AppsService } = await import(`${importPath}/apps/service`); + const { AppsUtilService } = await import(`${importPath}/apps/util.service`); + const { PageService } = await import(`${importPath}/apps/services/page.service`); + const { EventsService } = await import(`${importPath}/apps/services/event.service`); + const { ComponentsService } = await import(`${importPath}/apps/services/component.service`); + const { PageHelperService } = await import(`${importPath}/apps/services/page.util.service`); + + return { + module: ModulesModule, + imports: [ + await FolderAppsModule.register(configs), + await ThemesModule.register(configs), + await FoldersModule.register(configs), + await OrganizationsModule.register(configs), + await AppEnvironmentsModule.register(configs), + await DataSourcesModule.register(configs), + await AiModule.register(configs), + await AppPermissionsModule.register(configs), + ], + controllers: [ModulesController], + providers: [ + AppsService, + VersionRepository, + AppsRepository, + PageService, + EventsService, + AppsUtilService, + ComponentsService, + PageHelperService, + OrganizationRepository, + DataSourcesRepository, + ], + }; + } +} diff --git a/server/src/modules/modules/modules.controller.ts b/server/src/modules/modules/modules.controller.ts new file mode 100644 index 0000000000..705d81dece --- /dev/null +++ b/server/src/modules/modules/modules.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Post, UseGuards, Body } from '@nestjs/common'; +import { User } from '@modules/app/decorators/user.decorator'; +import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard'; +import { AppCreateDto } from '@modules/apps/dto'; +import { IModulesController } from '@modules/modules/IModulesController'; +import { InitModule } from '@modules/app/decorators/init-module'; +import { MODULES } from '@modules/app/constants/modules'; +import { InitFeature } from '@modules/app/decorators/init-feature.decorator'; +import { FEATURE_KEY } from '@modules/modules/constants'; + +@InitModule(MODULES.MODULES) +@Controller('modules') +export class ModulesController implements IModulesController { + @InitFeature(FEATURE_KEY.CREATE_MODULE) + @UseGuards(JwtAuthGuard) + @Post() + async create(@User() user, @Body() appCreateDto: AppCreateDto): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/server/src/modules/onboarding/constants/feature.ts b/server/src/modules/onboarding/constants/feature.ts index dde67c3912..b43028c4db 100644 --- a/server/src/modules/onboarding/constants/feature.ts +++ b/server/src/modules/onboarding/constants/feature.ts @@ -6,6 +6,7 @@ export const FEATURES: FeaturesConfig = { [MODULES.ONBOARDING]: { [FEATURE_KEY.ACTIVATE_ACCOUNT]: { isPublic: true, + auditLogsKey: 'USER_SIGNUP', }, // Account Activation [FEATURE_KEY.SETUP_SUPER_ADMIN]: { isPublic: true, @@ -15,6 +16,7 @@ export const FEATURES: FeaturesConfig = { }, // Signup [FEATURE_KEY.ACCEPT_INVITE]: { isPublic: true, + auditLogsKey: 'USER_INVITE_REDEEM', }, // Accept Invitation [FEATURE_KEY.RESEND_INVITE]: { isPublic: true, @@ -27,6 +29,7 @@ export const FEATURES: FeaturesConfig = { }, // Verify Organization Token [FEATURE_KEY.SETUP_ACCOUNT_FROM_TOKEN]: { isPublic: true, + auditLogsKey: 'USER_SIGNUP', }, // Setup Account From Token [FEATURE_KEY.CHECK_WORKSPACE_UNIQUENESS]: { isPublic: true, diff --git a/server/src/modules/onboarding/service.ts b/server/src/modules/onboarding/service.ts index c05c9502d2..33d6f9e5a7 100644 --- a/server/src/modules/onboarding/service.ts +++ b/server/src/modules/onboarding/service.ts @@ -120,7 +120,7 @@ export class OnboardingService implements IOnboardingService { const userParams = { email, password, firstName, lastName }; // Find the default workspace - const defaultWorkspace = await this.organizationRepository. getDefaultWorkspaceOfInstance(); + const defaultWorkspace = await this.organizationRepository.getDefaultWorkspaceOfInstance(); if (existingUser) { // Handling instance and workspace level signup for existing user @@ -133,7 +133,7 @@ export class OnboardingService implements IOnboardingService { manager ); } else { - if(defaultWorkspace && !signingUpOrganization) { + if (defaultWorkspace && !signingUpOrganization) { return await this.onboardingUtilService.createUserInDefaultWorkspace( userParams, defaultWorkspace, @@ -263,7 +263,8 @@ export class OnboardingService implements IOnboardingService { throw new BadRequestException('Please enter password'); } - const activateDefaultWorkspace = (defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId) || allowPersonalWorkspace; + const activateDefaultWorkspace = + (defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId) || allowPersonalWorkspace; if (activateDefaultWorkspace) { // Getting default workspace const defaultOrganizationUser: OrganizationUser = user.organizationUsers.find( @@ -277,11 +278,11 @@ export class OnboardingService implements IOnboardingService { // Activate default workspace await this.organizationUsersUtilService.activateOrganization(defaultOrganizationUser, manager); - if(defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId){ + if (defaultWorkspace && defaultWorkspace.id === user.defaultOrganizationId) { const personalWorkspaces = await this.organizationUsersUtilService.personalWorkspaces(user.id); - for(const personalWorkspace of personalWorkspaces){ + for (const personalWorkspace of personalWorkspaces) { // if any personal workspace left. activate those - await this.organizationUsersUtilService.activateOrganization(personalWorkspace, manager); + await this.organizationUsersUtilService.activateOrganization(personalWorkspace, manager); } } @@ -362,6 +363,9 @@ export class OnboardingService implements IOnboardingService { organizationId: organization?.id, resourceId: user.id, resourceName: user.email, + resourceData: { + signup_method: 'self-signup', + }, }); await this.licenseUserService.validateUser(manager); @@ -421,6 +425,13 @@ export class OnboardingService implements IOnboardingService { } const isWorkspaceSignup = organizationUser.source === WORKSPACE_USER_SOURCE.SIGNUP; await this.licenseUserService.validateUser(manager); + const auditLogEntry = { + userId: user.id, + organizationId: organization.id, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); return this.sessionUtilService.generateLoginResultPayload( response, user, @@ -534,6 +545,16 @@ export class OnboardingService implements IOnboardingService { Till now user doesn't have an organization. */ await this.licenseUserService.validateUser(manager); + const auditLogsData = { + userId: signupUser.id, + organizationId: signupUser.organizationUsers[0].organizationId, + resourceId: signupUser.id, + resourceName: signupUser.email, + resourceData: { + signup_method: 'invite-redemption', + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogsData); return this.onboardingUtilService.processOrganizationSignup( response, signupUser, @@ -566,7 +587,6 @@ export class OnboardingService implements IOnboardingService { if (user.status !== USER_STATUS.ACTIVE) { throw new BadRequestException(getUserErrorMessages(user.status)); } - RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { userId: user.id, organizationId: organizationUser.organizationId, diff --git a/server/src/modules/organization-users/constants/feature.ts b/server/src/modules/organization-users/constants/feature.ts index d3ee992027..27b9eb8f7e 100644 --- a/server/src/modules/organization-users/constants/feature.ts +++ b/server/src/modules/organization-users/constants/feature.ts @@ -6,12 +6,27 @@ export const FEATURES: FeaturesConfig = { [MODULES.ORGANIZATION_USER]: { [FEATURE_KEY.SUGGEST_USERS]: {}, [FEATURE_KEY.VIEW_ALL_USERS]: {}, - [FEATURE_KEY.USER_ARCHIVE_ALL]: {}, - [FEATURE_KEY.USER_ARCHIVE]: {}, - [FEATURE_KEY.USER_INVITE]: {}, + [FEATURE_KEY.USER_ARCHIVE_ALL]: { + isPublic: true, + auditLogsKey: 'USER_ARCHIVE', + }, + [FEATURE_KEY.USER_ARCHIVE]: { + isPublic: true, + auditLogsKey: 'USER_ARCHIVE', + }, + [FEATURE_KEY.USER_INVITE]: { + isPublic: true, + auditLogsKey: 'USER_INVITE', + }, [FEATURE_KEY.USER_BULK_UPLOAD]: {}, - [FEATURE_KEY.USER_UNARCHIVE]: {}, - [FEATURE_KEY.USER_UNARCHIVE_ALL]: {}, + [FEATURE_KEY.USER_UNARCHIVE]: { + isPublic: true, + auditLogsKey: 'USER_UNARCHIVE', + }, + [FEATURE_KEY.USER_UNARCHIVE_ALL]: { + isPublic: true, + auditLogsKey: 'USER_UNARCHIVE', + }, [FEATURE_KEY.USER_UPDATE]: {}, }, }; diff --git a/server/src/modules/organization-users/controller.ts b/server/src/modules/organization-users/controller.ts index 27d1d20b84..43d6c78af9 100644 --- a/server/src/modules/organization-users/controller.ts +++ b/server/src/modules/organization-users/controller.ts @@ -90,14 +90,14 @@ export class OrganizationUsersController implements IOrganizationUsersController if (user.id === userId) { throw new NotAcceptableException('Self archive not allowed'); } - await this.organizationUsersService.archiveFromAll(userId); + await this.organizationUsersService.archiveFromAll(userId, user); return; } @InitFeature(FEATURE_KEY.USER_UNARCHIVE_ALL) @Post(':userId/unarchive-all') async unarchiveAll(@User() user: UserEntity, @Param('userId') userId: string) { - await this.organizationUsersService.unarchiveUser(userId); + await this.organizationUsersService.unarchiveUser(userId, user); return; } diff --git a/server/src/modules/organization-users/interfaces/IService.ts b/server/src/modules/organization-users/interfaces/IService.ts index 57adc96882..bbed4de3c2 100644 --- a/server/src/modules/organization-users/interfaces/IService.ts +++ b/server/src/modules/organization-users/interfaces/IService.ts @@ -6,8 +6,8 @@ import { UpdateOrgUserDto } from '../dto'; export interface IOrganizationUsersService { updateOrgUser(organizationUserId: string, user: User, updateOrgUserDto: UpdateOrgUserDto): Promise; archive(id: string, organizationId: string, user?: User): Promise; - archiveFromAll(userId: string): Promise; - unarchiveUser(userId: string): Promise; + archiveFromAll(userId: string, user: User): Promise; + unarchiveUser(userId: string, user: User): Promise; unarchive(user: User, id: string, organizationId: string): Promise; inviteNewUser(currentUser: User, inviteNewUserDto: InviteNewUserDto): Promise; bulkUploadUsers(currentUser: User, fileStream: any, res: Response): Promise; diff --git a/server/src/modules/organization-users/service.ts b/server/src/modules/organization-users/service.ts index c4ed443dfa..467b5e9539 100644 --- a/server/src/modules/organization-users/service.ts +++ b/server/src/modules/organization-users/service.ts @@ -24,6 +24,9 @@ import { Response } from 'express'; import { UserCsvRow } from './interfaces'; import { IOrganizationUsersService } from './interfaces/IService'; import { UpdateOrgUserDto } from './dto'; +import { RequestContext } from '@modules/request-context/service'; +import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; +import { Organization } from '@entities/organization.entity'; @Injectable() export class OrganizationUsersService implements IOrganizationUsersService { constructor( @@ -38,7 +41,6 @@ export class OrganizationUsersService implements IOrganizationUsersService { async updateOrgUser(organizationUserId: string, user: User, updateOrgUserDto: UpdateOrgUserDto) { const { firstName, lastName, addGroups, role, userMetadata } = updateOrgUserDto; - const organizationUser = await this.organizationUsersRepository.findOne({ where: { id: organizationUserId, organizationId: user.organizationId }, }); @@ -81,35 +83,84 @@ export class OrganizationUsersService implements IOrganizationUsersService { } async archive(id: string, organizationId: string, user?: User): Promise { - const organizationUser = await this.organizationUsersRepository.findOneOrFail({ - where: { id, organizationId }, - relations: ['user'], - }); + await dbTransactionWrap(async (manager: EntityManager) => { + const organizationUser = await manager.findOneOrFail(OrganizationUser, { + where: { id, organizationId }, + relations: ['user'], + }); - await this.organizationUsersUtilService.throwErrorIfUserIsLastActiveAdmin(organizationUser?.user, organizationId); - await this.organizationUsersRepository.update(id, { - status: WORKSPACE_USER_STATUS.ARCHIVED, - invitationToken: null, + await this.organizationUsersUtilService.throwErrorIfUserIsLastActiveAdmin(organizationUser?.user, organizationId); + await manager.update(OrganizationUser, id, { + status: WORKSPACE_USER_STATUS.ARCHIVED, + invitationToken: null, + }); + const organization = await manager.findOne(Organization, { + where: { id: organizationUser.organizationId }, + }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: organizationUser.user.email, + resourceData: { + archived_user: { + id: organizationUser.userId, + email: organizationUser.user.email, + first_name: organizationUser.user.firstName, + last_name: organizationUser.user.lastName, + }, + archived_user_workspace: { + workspace_name: organization.name, + workspace_id: organization.id, + }, + }, + }; + + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } - async archiveFromAll(userId: string): Promise { + async archiveFromAll(userId: string, user: User): Promise { await dbTransactionWrap(async (manager: EntityManager) => { + const archivedUserWorkspaces = await manager.find(OrganizationUser, { + where: { userId }, + relations: ['user'], + }); await manager.update( OrganizationUser, { userId }, { status: WORKSPACE_USER_STATUS.ARCHIVED, invitationToken: null } ); await this.organizationUsersUtilService.updateUserStatus(userId, USER_STATUS.ARCHIVED, manager); + const organizationIds = archivedUserWorkspaces.map((user) => user.organizationId); + const auditLogEntry = { + userId: user.id, + organizationIds: organizationIds, + resourceId: user.id, + resourceName: archivedUserWorkspaces[0].user.email, + resourceData: { + archived_user: { + id: archivedUserWorkspaces[0].userId, + email: archivedUserWorkspaces[0].user.email, + first_name: archivedUserWorkspaces[0].user.firstName, + last_name: archivedUserWorkspaces[0].user.lastName, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } - async unarchiveUser(userId: string): Promise { + async unarchiveUser(userId: string, user: User): Promise { await dbTransactionWrap(async (manager: EntityManager) => { const targetUser = await manager.findOneOrFail(User, { where: { id: userId }, select: ['id', 'status', 'invitationToken', 'source'], }); + const unarchivedUserWorkspaces = await manager.find(OrganizationUser, { + where: { userId }, + relations: ['user'], + }); const { status, invitationToken } = targetUser; /* Special case. what if the user is archived when the status is invited. we were changing status to active before */ const updatedStatus = @@ -117,6 +168,22 @@ export class OrganizationUsersService implements IOrganizationUsersService { await this.organizationUsersUtilService.updateUserStatus(userId, updatedStatus, manager); await this.licenseUserService.validateUser(manager); await this.licenseOrganizationService.validateOrganization(manager); + const organizationIds = unarchivedUserWorkspaces.map((user) => user.organizationId); + const auditLogEntry = { + userId: user.id, + organizationIds: organizationIds, + resourceId: user.id, + resourceName: unarchivedUserWorkspaces[0].user.email, + resourceData: { + unarchived_user: { + id: unarchivedUserWorkspaces[0].userId, + email: unarchivedUserWorkspaces[0].user.email, + first_name: unarchivedUserWorkspaces[0].user.firstName, + last_name: unarchivedUserWorkspaces[0].user.lastName, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } @@ -144,6 +211,29 @@ export class OrganizationUsersService implements IOrganizationUsersService { await this.licenseUserService.validateUser(manager); await this.licenseOrganizationService.validateOrganization(manager); + const organization = await manager.findOne(Organization, { + where: { id: organizationUser.organizationId }, + }); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: organizationUser.user.email, + resourceData: { + unarchived_user: { + id: organizationUser.userId, + email: organizationUser.user.email, + first_name: organizationUser.user.firstName, + last_name: organizationUser.user.lastName, + }, + unarchived_user_workspace: { + workspace_name: organization.name, + workspace_id: organization.id, + }, + }, + }; + + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); if (organizationUser.user.invitationToken) { @@ -160,6 +250,7 @@ export class OrganizationUsersService implements IOrganizationUsersService { sender: user.firstName, }, }); + return; } diff --git a/server/src/modules/organization-users/util.service.ts b/server/src/modules/organization-users/util.service.ts index c040e2cff7..ba6deb3d65 100644 --- a/server/src/modules/organization-users/util.service.ts +++ b/server/src/modules/organization-users/util.service.ts @@ -1,7 +1,7 @@ import { User } from '@entities/user.entity'; import { dbTransactionWrap } from '@helpers/database.helper'; import { fullName, generateNextNameAndSlug } from '@helpers/utils.helper'; -import { EntityManager } from 'typeorm'; +import { EntityManager, In } from 'typeorm'; import { getUserStatusAndSource, lifecycleEvents, @@ -31,8 +31,6 @@ import { UserDetailsService } from './services/user-details.service'; import { FetchUserResponse, InvitedUserType, RoleUpdate, UserFilterOptions } from './types'; import { GroupPermissionsRepository } from '@modules/group-permissions/repository'; import { ERROR_HANDLER, ERROR_HANDLER_TITLE } from '@modules/organizations/constants'; -import { MODULE_INFO } from '@modules/app/constants/module-info'; -import { MODULES } from '@modules/app/constants/modules'; import { INSTANCE_USER_SETTINGS } from '@modules/instance-settings/constants'; import { OrganizationRepository } from '@modules/organizations/repository'; import * as uuid from 'uuid'; @@ -512,11 +510,33 @@ export class OrganizationUsersUtilService implements IOrganizationUsersUtilServi !user || !!user.invitationToken ); + const groupsArray = []; + if (inviteNewUserDto.groups && inviteNewUserDto.groups.length > 0) { + const groupQuery = { + organizationId: currentOrganization.id, + id: In(inviteNewUserDto.groups), + }; + const orgGroupPermissions = await this.groupPermissionsRepository.find({ + where: groupQuery, + select: ['id', 'name'], + }); + groupsArray.push(...orgGroupPermissions.map((group) => group.name)); + } RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { userId: currentUser.id, organizationId: currentOrganization.id, - resourceId: currentOrganization.id, + resourceId: updatedUser.id, resourceName: updatedUser.email, + resourceData: { + invited_user: { + id: updatedUser.id, + email: updatedUser.email, + first_name: updatedUser.firstName, + last_name: updatedUser.lastName, + role: inviteNewUserDto.role, + group: groupsArray, + }, + }, }); return organizationUser; diff --git a/server/src/modules/plugins/ability/index.ts b/server/src/modules/plugins/ability/index.ts index 1bdc0c1a96..9c31bb44e0 100644 --- a/server/src/modules/plugins/ability/index.ts +++ b/server/src/modules/plugins/ability/index.ts @@ -4,6 +4,7 @@ import { AbilityFactory } from '@modules/app/ability-factory'; import { UserAllPermissions } from '@modules/app/types'; import { FEATURE_KEY } from '../constants'; import { Plugin } from '@entities/plugin.entity'; +import { getTooljetEdition } from '@helpers/utils.helper'; type Subjects = InferSubjects | 'all'; export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>; @@ -16,17 +17,14 @@ export class FeatureAbilityFactory extends AbilityFactory protected defineAbilityFor(can: AbilityBuilder['can'], UserAllPermissions: UserAllPermissions): void { const { superAdmin, isAdmin, isBuilder } = UserAllPermissions; + const toolJetEdition = getTooljetEdition(); + if ((toolJetEdition == 'ee' && superAdmin) || (toolJetEdition !== 'ee' && isAdmin)) { + can([FEATURE_KEY.UNINSTALL_PLUGINS, FEATURE_KEY.DELETE], Plugin); + } + if (superAdmin || isAdmin || isBuilder) { - // Admin, super admin and Builder can do all operations can( - [ - FEATURE_KEY.INSTALL, - FEATURE_KEY.UPDATE, - FEATURE_KEY.DELETE, - FEATURE_KEY.INSTALL_DEPENDENT_PLUGINS, - FEATURE_KEY.UNINSTALL_PLUGINS, - FEATURE_KEY.DEPENDENT_PLUGINS, - ], + [FEATURE_KEY.INSTALL, FEATURE_KEY.UPDATE, FEATURE_KEY.INSTALL_DEPENDENT_PLUGINS, FEATURE_KEY.DEPENDENT_PLUGINS], Plugin ); } diff --git a/server/src/modules/plugins/module.ts b/server/src/modules/plugins/module.ts index ccc59f525e..a8e8b7f7ec 100644 --- a/server/src/modules/plugins/module.ts +++ b/server/src/modules/plugins/module.ts @@ -2,6 +2,7 @@ import { FilesRepository } from '@modules/files/repository'; import { getImportPath } from '@modules/app/constants'; import { DynamicModule } from '@nestjs/common'; import { FeatureAbilityFactory } from './ability'; +import { DataSourcesRepository } from '@modules/data-sources/repository'; export class PluginsModule { static async register(configs: { IS_GET_CONTEXT: boolean }): Promise { @@ -13,7 +14,7 @@ export class PluginsModule { return { module: PluginsModule, controllers: [PluginsController], - providers: [PluginsService, FilesRepository, PluginsUtilService, FeatureAbilityFactory], + providers: [PluginsService, FilesRepository, PluginsUtilService, FeatureAbilityFactory, DataSourcesRepository], exports: [PluginsUtilService], }; } diff --git a/server/src/modules/plugins/service.ts b/server/src/modules/plugins/service.ts index 48398d5ebe..ab1c7fc2bf 100644 --- a/server/src/modules/plugins/service.ts +++ b/server/src/modules/plugins/service.ts @@ -1,4 +1,4 @@ -import { InternalServerErrorException, NotFoundException } from '@nestjs/common'; +import { BadRequestException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { CreatePluginDto, UpdatePluginDto } from './dto'; import { PluginsUtilService } from './util.service'; import { dbTransactionWrap } from '@helpers/database.helper'; @@ -10,17 +10,25 @@ import { FilesRepository } from '@modules/files/repository'; import { IPluginsService } from './interfaces/IService'; import * as path from 'path'; import { Injectable } from '@nestjs/common'; +import { DataSourcesRepository } from '@modules/data-sources/repository'; const fs = require('fs'); @Injectable() export class PluginsService implements IPluginsService { constructor( protected readonly pluginsUtilService: PluginsUtilService, - protected readonly fileRepository: FilesRepository + protected readonly fileRepository: FilesRepository, + protected readonly dataSourcesRepository: DataSourcesRepository ) {} async install(body: CreatePluginDto) { - const { id, repo } = body; + const { id, repo, name } = body; + + const existingPlugin = await dbTransactionWrap((manager: EntityManager) => { + return manager.findOne(Plugin, { where: { pluginId: id } }); + }); + if (existingPlugin) throw new BadRequestException(`Plugin '${name}' is already installed.`); + const [index, operations, icon, manifest, version] = await this.pluginsUtilService.fetchPluginFiles(id, repo); let shouldCreate = false; @@ -47,7 +55,7 @@ export class PluginsService implements IPluginsService { async findOne(id: string) { return dbTransactionWrap((manager: EntityManager) => { - return manager.find(Plugin, { where: { id } }); + return manager.findOne(Plugin, { where: { id } }); }); } @@ -57,7 +65,18 @@ export class PluginsService implements IPluginsService { return await this.pluginsUtilService.upgrade(id, body, version, { index, operations, icon, manifest }); } - remove(id: string) { + async remove(id: string) { + const dataSourcesByMarketplacePlugin = await this.dataSourcesRepository.getDatasourceByPluginId(id); + if (dataSourcesByMarketplacePlugin.length) { + const queries = []; + dataSourcesByMarketplacePlugin?.forEach((datasource) => { + if (datasource.dataQueries.length) queries.push(...datasource.dataQueries); + }); + if (queries.length) { + throw new InternalServerErrorException(`Plugin can't be removed, queries of plugin are in use`); + } + } + return dbTransactionWrap((manager: EntityManager) => { return manager.delete(Plugin, id); }); diff --git a/server/src/modules/profile/constants/feature.ts b/server/src/modules/profile/constants/feature.ts index 0ef0b4f1c9..9b5ba2b08d 100644 --- a/server/src/modules/profile/constants/feature.ts +++ b/server/src/modules/profile/constants/feature.ts @@ -4,9 +4,18 @@ import { FeaturesConfig } from '../types'; export const FEATURES: FeaturesConfig = { [MODULES.PROFILE]: { - [FEATURE_KEY.UPDATE_AVATAR]: {}, + [FEATURE_KEY.UPDATE_AVATAR]: { + isPublic: true, + auditLogsKey: 'USER_PROFILE_UPDATE', + }, [FEATURE_KEY.GET]: {}, - [FEATURE_KEY.UPDATE]: {}, - [FEATURE_KEY.UPDATE_PASSWORD]: {}, + [FEATURE_KEY.UPDATE]: { + isPublic: true, + auditLogsKey: 'USER_PROFILE_UPDATE', + }, + [FEATURE_KEY.UPDATE_PASSWORD]: { + isPublic: true, + auditLogsKey: 'USER_PASSWORD_UPDATE', + }, }, }; diff --git a/server/src/modules/profile/service.ts b/server/src/modules/profile/service.ts index 9ac2979568..17ab95d476 100644 --- a/server/src/modules/profile/service.ts +++ b/server/src/modules/profile/service.ts @@ -7,6 +7,8 @@ import { ProfileUtilService } from '@modules/profile/util.service'; import { User } from '@entities/user.entity'; import { IProfileService } from '@modules/profile/interfaces/IService'; import { File } from '@entities/file.entity'; +import { RequestContext } from '@modules/request-context/service'; +import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; @Injectable() export class ProfileService implements IProfileService { @@ -25,19 +27,76 @@ export class ProfileService implements IProfileService { async addAvatar(userId: string, imageBuffer: Buffer, filename: string): Promise { return dbTransactionWrap(async (manager: EntityManager) => { - return this.serviceUtils.addAvatar(userId, imageBuffer, filename, manager); + const user = await this.userRepository.getUser({ + id: userId, + }); + const avatar = await this.serviceUtils.addAvatar(userId, imageBuffer, filename, manager); + const auditLogData = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + resourceData: { + previous_user_details: { + avatar_id: user.avatarId, + }, + updated_user_details: { + avatar_id: avatar.id, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogData); + return avatar; }); } async updateUserPassword(userId: string, password: string): Promise { - await this.userRepository.updateOne(userId, { - password, - passwordRetryCount: 0, + return dbTransactionWrap(async (manager: EntityManager) => { + const user = await manager.findOneOrFail(User, { + where: { id: userId }, + }); + await this.userRepository.updateOne( + userId, + { + password, + passwordRetryCount: 0, + }, + manager + ); + const auditLogEntry = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogEntry); }); } async updateUserName(userId: string, updateUserDto: ProfileUpdateDto): Promise { - const { first_name: firstName, last_name: lastName } = updateUserDto; - await this.userRepository.updateOne(userId, { firstName, lastName }); + return dbTransactionWrap(async (manager: EntityManager) => { + const user = await manager.findOneOrFail(User, { + where: { id: userId }, + }); + const { first_name: firstName, last_name: lastName } = updateUserDto; + await this.userRepository.updateOne(userId, { firstName, lastName }, manager); + const auditLogData = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + resourceData: { + previous_user_details: { + first_name: user.firstName, + last_name: user.lastName, + }, + updated_user_details: { + first_name: firstName, + last_name: lastName, + }, + }, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogData); + }); } } diff --git a/server/src/modules/profile/util.service.ts b/server/src/modules/profile/util.service.ts index 671ef60a85..2631627e2b 100644 --- a/server/src/modules/profile/util.service.ts +++ b/server/src/modules/profile/util.service.ts @@ -8,7 +8,7 @@ import { CreateFileDto } from '@modules/files/dto/index'; @Injectable() export class ProfileUtilService implements IProfileUtilService { - constructor(protected readonly filesRepository: FilesRepository, protected userRepository: UserRepository) {} + constructor(protected readonly filesRepository: FilesRepository, protected readonly userRepository: UserRepository) {} async addAvatar(userId: string, imageBuffer: Buffer, filename: string, manager?: EntityManager): Promise { const user = await this.userRepository.getUser({ @@ -31,6 +31,7 @@ export class ProfileUtilService implements IProfileUtilService { if (currentAvatarId) { await this.filesRepository.removeOne(currentAvatarId, manager); } + return avatar; } } diff --git a/server/src/modules/session/constants/feature.ts b/server/src/modules/session/constants/feature.ts index fd482bcfd6..9c887c9355 100644 --- a/server/src/modules/session/constants/feature.ts +++ b/server/src/modules/session/constants/feature.ts @@ -4,7 +4,10 @@ import { FeaturesConfig } from '../types'; export const FEATURES: FeaturesConfig = { [MODULES.SESSION]: { - [FEATURE_KEY.LOG_OUT]: {}, + [FEATURE_KEY.LOG_OUT]: { + isPublic: true, + auditLogsKey: 'USER_LOGOUT', + }, [FEATURE_KEY.GET_INVITED_USER_SESSION]: { isPublic: true, }, diff --git a/server/src/modules/session/service.ts b/server/src/modules/session/service.ts index ba0b089aa9..3aa0aedaf4 100644 --- a/server/src/modules/session/service.ts +++ b/server/src/modules/session/service.ts @@ -19,6 +19,8 @@ import { OrganizationRepository } from '@modules/organizations/repository'; import { OrganizationUsersRepository } from '@modules/organization-users/repository'; import { fullName, generateOrgInviteURL, isSuperAdmin } from '@helpers/utils.helper'; import { decamelizeKeys } from 'humps'; +import { RequestContext } from '@modules/request-context/service'; +import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; @Injectable() export class SessionService { @@ -34,6 +36,17 @@ export class SessionService { response.clearCookie('tj_auth_token'); await dbTransactionWrap(async (manager: EntityManager) => { await manager.delete(UserSessions, { id: sessionId, userId }); + const user = await manager.findOneOrFail(User, { + where: { id: userId }, + }); + + const auditLogData = { + userId: user.id, + organizationId: user.defaultOrganizationId, + resourceId: user.id, + resourceName: user.email, + }; + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, auditLogData); }); } diff --git a/server/src/modules/session/util.service.ts b/server/src/modules/session/util.service.ts index 282a6179e5..fcfb61d9f0 100644 --- a/server/src/modules/session/util.service.ts +++ b/server/src/modules/session/util.service.ts @@ -44,6 +44,7 @@ export class SessionUtilService { protected readonly encryptionService: EncryptionService, protected readonly jwtService: JwtService ) {} + async terminateAllSessions(userId: string): Promise { await dbTransactionWrap(async (manager: EntityManager) => { await manager.delete(UserSessions, { userId }); @@ -333,7 +334,7 @@ export class SessionUtilService { }) : null; - const noWorkspaceAttachedInTheSession = await this.checkUserWorkspaceStatus(user.id) && !isSuperAdmin(user); + const noWorkspaceAttachedInTheSession = (await this.checkUserWorkspaceStatus(user.id)) && !isSuperAdmin(user); const isAllWorkspacesArchived = await this.#isAllWorkspacesArchivedBySuperAdmin(user.id); const onboardingFlags = await this.#onboardingFlags(user); const metadata = await this.metadataUtilService.fetchMetadata(); @@ -367,7 +368,7 @@ export class SessionUtilService { async #onboardingFlags(user: User) { let isFirstUserOnboardingCompleted = true; - let isOnboardingCompleted = true; + const isOnboardingCompleted = true; // const isOnboardingQuestionsEnabled = // this.configService.get('ENABLE_ONBOARDING_QUESTIONS_FOR_ALL_SIGN_UPS') === 'true'; diff --git a/server/src/modules/users/constants/features.ts b/server/src/modules/users/constants/features.ts index cc68400c25..1524e517a1 100644 --- a/server/src/modules/users/constants/features.ts +++ b/server/src/modules/users/constants/features.ts @@ -5,8 +5,21 @@ import { FeaturesConfig } from '../types'; export const FEATURES: FeaturesConfig = { [MODULES.USER]: { [FEATURE_KEY.GET_ALL_USERS]: {}, - [FEATURE_KEY.UPDATE_USER_TYPE]: {}, - [FEATURE_KEY.AUTO_UPDATE_USER_PASSWORD]: {}, - [FEATURE_KEY.CHANGE_USER_PASSWORD]: {}, + [FEATURE_KEY.UPDATE_USER_TYPE]: { + isPublic: true, + auditLogsKey: 'USER_DETAILS_UPDATE', + }, + [FEATURE_KEY.UPDATE_USER_TYPE_INSTANCE]: { + isPublic: true, + auditLogsKey: 'SET_AS_SUPERADMIN', + }, + [FEATURE_KEY.AUTO_UPDATE_USER_PASSWORD]: { + isPublic: true, + auditLogsKey: 'USER_PASSWORD_RESET', + }, + [FEATURE_KEY.CHANGE_USER_PASSWORD]: { + isPublic: true, + auditLogsKey: 'USER_PASSWORD_RESET', + }, }, }; diff --git a/server/src/modules/users/constants/index.ts b/server/src/modules/users/constants/index.ts index 0808b21fc1..9e14e275cf 100644 --- a/server/src/modules/users/constants/index.ts +++ b/server/src/modules/users/constants/index.ts @@ -3,4 +3,5 @@ export enum FEATURE_KEY { UPDATE_USER_TYPE = 'UPDATE_USER_TYPE', AUTO_UPDATE_USER_PASSWORD = 'AUTO_UPDATE_USER_PASSWORD', CHANGE_USER_PASSWORD = 'CHANGE_USER_PASSWORD', + UPDATE_USER_TYPE_INSTANCE = 'UPDATE_USER_TYPE_INSTANCE', } diff --git a/server/src/modules/users/controller.ts b/server/src/modules/users/controller.ts index 145320a22e..31d62ac5de 100644 --- a/server/src/modules/users/controller.ts +++ b/server/src/modules/users/controller.ts @@ -2,19 +2,20 @@ import { Controller, NotFoundException } from '@nestjs/common'; import { UpdateUserTypeDto } from '@modules/onboarding/dto/user.dto'; import { IUserController } from './interfaces/IController'; import { ChangePasswordDto } from './dto'; +import { User } from '@entities/user.entity'; @Controller('users') export class UsersController implements IUserController { getAllUsers(query: { page?: number; searchText?: string; status?: string }): Promise { throw new NotFoundException(); } - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise { + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise { throw new NotFoundException(); } - autoUpdateUserPassword(userId: string): Promise<{ newPassword: string }> { + autoUpdateUserPassword(userId: string, user: User): Promise<{ newPassword: string }> { throw new NotFoundException(); } - changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto): Promise { + changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto, user: User): Promise { throw new NotFoundException(); } } diff --git a/server/src/modules/users/dto/index.ts b/server/src/modules/users/dto/index.ts index d7b5b1a450..ea2a8f58f0 100644 --- a/server/src/modules/users/dto/index.ts +++ b/server/src/modules/users/dto/index.ts @@ -11,12 +11,6 @@ export class UpdateUserTypeDto { @MaxLength(100) userId: string; - @IsNotEmpty() - @IsString() - @Transform(({ value }) => sanitizeInput(value)) - @MaxLength(100) - userType: USER_TYPE; - @IsOptional() @IsString() @Transform(({ value }) => sanitizeInput(value)) @@ -27,6 +21,19 @@ export class UpdateUserTypeDto { @Transform(({ value }) => sanitizeInput(value)) lastName: string; } +export class UpdateUserTypeInstanceDto { + @IsNotEmpty() + @IsString() + @Transform(({ value }) => sanitizeInput(value)) + @MaxLength(100) + userId: string; + + @IsNotEmpty() + @IsString() + @Transform(({ value }) => sanitizeInput(value)) + @MaxLength(100) + userType: USER_TYPE; +} @Exclude() export class AllUserResponse { diff --git a/server/src/modules/users/interfaces/IController.ts b/server/src/modules/users/interfaces/IController.ts index 793b7d2738..80b9ce9119 100644 --- a/server/src/modules/users/interfaces/IController.ts +++ b/server/src/modules/users/interfaces/IController.ts @@ -1,11 +1,12 @@ +import { User } from '@entities/user.entity'; import { UpdateUserTypeDto, ChangePasswordDto } from '../dto'; export interface IUserController { getAllUsers(query: { page?: number; searchText?: string; status?: string }): Promise; - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise; + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise; - autoUpdateUserPassword(userId: string): Promise<{ newPassword: string }>; + autoUpdateUserPassword(userId: string, user: User): Promise<{ newPassword: string }>; - changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto): Promise; + changeUserPassword(userId: string, changePasswordDto: ChangePasswordDto, user: User): Promise; } diff --git a/server/src/modules/users/interfaces/IService.ts b/server/src/modules/users/interfaces/IService.ts index fe3cd48469..9676fd049e 100644 --- a/server/src/modules/users/interfaces/IService.ts +++ b/server/src/modules/users/interfaces/IService.ts @@ -1,3 +1,4 @@ +import { User } from '@entities/user.entity'; import { AllUserResponse, UpdateUserTypeDto } from '@modules/onboarding/dto/user.dto'; export interface IUsersService { @@ -6,9 +7,9 @@ export interface IUsersService { users: AllUserResponse[]; }>; - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise; + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise; - updatePassword(userId: string, password: string): Promise; + updatePassword(userId: string, user: User, password: string): Promise; - autoUpdateUserPassword(userId: string): Promise; + autoUpdateUserPassword(userId: string, user: User): Promise; } diff --git a/server/src/modules/users/module.ts b/server/src/modules/users/module.ts index 963eb08fca..9ba2992f32 100644 --- a/server/src/modules/users/module.ts +++ b/server/src/modules/users/module.ts @@ -3,6 +3,7 @@ import { DynamicModule } from '@nestjs/common'; import { UserRepository } from './repository'; import { SessionModule } from '@modules/session/module'; import { FeatureAbilityFactory } from './ability'; +import { OrganizationUsersRepository } from '@modules/organization-users/repository'; export class UsersModule { static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise { @@ -15,7 +16,7 @@ export class UsersModule { module: UsersModule, imports: [await SessionModule.register(configs)], controllers: [UsersController], - providers: [UsersService, UserRepository, UsersUtilService, FeatureAbilityFactory], + providers: [UsersService, UserRepository, UsersUtilService, FeatureAbilityFactory, OrganizationUsersRepository], exports: [UsersUtilService], }; } diff --git a/server/src/modules/users/service.ts b/server/src/modules/users/service.ts index c1870f1f4d..a422f99a68 100644 --- a/server/src/modules/users/service.ts +++ b/server/src/modules/users/service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { AllUserResponse, UpdateUserTypeDto } from '@modules/onboarding/dto/user.dto'; import { IUsersService } from '@modules/users/interfaces/IService'; +import { User } from '@entities/user.entity'; @Injectable() export class UsersService implements IUsersService { @@ -9,13 +10,13 @@ export class UsersService implements IUsersService { ): Promise<{ meta: { total_pages: number; total_count: number; current_page: number }; users: AllUserResponse[] }> { throw new Error('Method not implemented.'); } - updateUserType(updateUserTypeDto: UpdateUserTypeDto): Promise { + updateUserType(updateUserTypeDto: UpdateUserTypeDto, user: User): Promise { throw new Error('Method not implemented.'); } - updatePassword(userId: string, password: string): Promise { + updatePassword(userId: string, user: User, password: string): Promise { throw new Error('Method not implemented.'); } - autoUpdateUserPassword(userId: string): Promise { + autoUpdateUserPassword(userId: string, user: User): Promise { throw new Error('Method not implemented.'); } } diff --git a/server/src/modules/users/types.ts b/server/src/modules/users/types.ts index c05429936a..37319f7b43 100644 --- a/server/src/modules/users/types.ts +++ b/server/src/modules/users/types.ts @@ -7,6 +7,7 @@ interface Features { [FEATURE_KEY.UPDATE_USER_TYPE]: FeatureConfig; [FEATURE_KEY.AUTO_UPDATE_USER_PASSWORD]: FeatureConfig; [FEATURE_KEY.CHANGE_USER_PASSWORD]: FeatureConfig; + [FEATURE_KEY.UPDATE_USER_TYPE_INSTANCE]: FeatureConfig; } export interface FeaturesConfig { diff --git a/server/src/modules/versions/repository.ts b/server/src/modules/versions/repository.ts index 4cd26d0202..f79791b6c7 100644 --- a/server/src/modules/versions/repository.ts +++ b/server/src/modules/versions/repository.ts @@ -179,4 +179,20 @@ export class VersionRepository extends Repository { return appVersion.app; }, manager || this.manager); } + + async findVersionsFromApp(app: App, manager?: EntityManager): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const appVersions = await manager.find(AppVersion, { + where: { appId: app.id }, + relations: [ + 'app', + 'dataQueries', + 'dataQueries.dataSource', + 'dataQueries.plugins', + 'dataQueries.plugins.manifestFile', + ], + }); + return appVersions; + }, manager || this.manager); + } } diff --git a/server/src/modules/versions/service.ts b/server/src/modules/versions/service.ts index 5ab17efd5c..84620d446c 100644 --- a/server/src/modules/versions/service.ts +++ b/server/src/modules/versions/service.ts @@ -108,62 +108,78 @@ export class VersionService implements IVersionService { } async getVersion(app: App, user: User): Promise { - const versionId = app.appVersions[0].id; - const appVersion = await this.versionRepository.findVersion(versionId); - - const pagesForVersion = await this.pageService.findPagesForVersion(versionId); - const eventsForVersion = await this.eventsService.findEventsForVersion(versionId); - - const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion)); - - if ( - appCurrentEditingVersion && - !(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT)) - ) { - const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId); - appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id; - } - - let shouldFreezeEditor = false; - if (appCurrentEditingVersion) { - const hasMultiEnvLicense = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT); - if (hasMultiEnvLicense) { - const currentEnvironment = await this.appEnvironmentUtilService.get( - user.organizationId, - appCurrentEditingVersion['currentEnvironmentId'] - ); - shouldFreezeEditor = currentEnvironment.priority > 1; + const prepareResponse = async (app: App, versionId: string) => { + let appVersion, + updatedVersionId = versionId; + if (updatedVersionId) { + appVersion = await this.versionRepository.findVersion(updatedVersionId); } else { + appVersion = await this.versionRepository.findVersionsFromApp(app); + appVersion = appVersion[0]; + updatedVersionId = appVersion.id; + } + + const pagesForVersion = await this.pageService.findPagesForVersion(updatedVersionId); + const eventsForVersion = await this.eventsService.findEventsForVersion(updatedVersionId); + + const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion)); + + if ( + appCurrentEditingVersion && + !(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT)) + ) { const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId); appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id; } - } - delete appCurrentEditingVersion['app']; + let shouldFreezeEditor = false; + if (appCurrentEditingVersion) { + const hasMultiEnvLicense = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT); + if (hasMultiEnvLicense) { + const currentEnvironment = await this.appEnvironmentUtilService.get( + user.organizationId, + appCurrentEditingVersion['currentEnvironmentId'] + ); + shouldFreezeEditor = currentEnvironment.priority > 1; + } else { + const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId); + appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id; + } + } - const appData = { - ...app, + delete appCurrentEditingVersion['app']; + + const appData = { + ...app, + }; + + delete appData['editingVersion']; + + const editingVersion = camelizeKeys(appCurrentEditingVersion); + + // Inject app theme + const appTheme = await this.organizationThemesUtilService.getTheme( + user.organizationId, + editingVersion?.globalSettings?.theme?.id + ); + + editingVersion['globalSettings']['theme'] = appTheme; + + return { + ...appData, + editing_version: editingVersion, + pages: this.appUtilService.mergeDefaultComponentData(pagesForVersion), + events: eventsForVersion, + should_freeze_editor: app.creationMode === 'GIT' || shouldFreezeEditor, + }; }; - delete appData['editingVersion']; + const response = await prepareResponse(app, app.appVersions?.[0]?.id); + const modules = await this.appUtilService.fetchModules(app, false, undefined); - const editingVersion = camelizeKeys(appCurrentEditingVersion); + response['modules'] = await Promise.all(modules.map((module) => prepareResponse(module, undefined))); - // Inject app theme - const appTheme = await this.organizationThemesUtilService.getTheme( - user.organizationId, - editingVersion?.globalSettings?.theme?.id - ); - - editingVersion['globalSettings']['theme'] = appTheme; - - return { - ...appData, - editing_version: editingVersion, - pages: this.appUtilService.mergeDefaultComponentData(pagesForVersion), - events: eventsForVersion, - should_freeze_editor: app.creationMode === 'GIT' || shouldFreezeEditor, - }; + return response; } async update(app: App, user: User, appVersionUpdateDto: AppVersionUpdateDto) {