mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 00:48:25 +00:00
Merge branch 'appbuilder/sprint-14' into feat/query-permissions
This commit is contained in:
commit
7f249aeb13
330 changed files with 14191 additions and 2633 deletions
197
.github/workflows/cypress-platform.yml
vendored
197
.github/workflows/cypress-platform.yml
vendored
|
|
@ -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()
|
||||
|
|
|
|||
72
.github/workflows/maketplace-plugins-deploy.yml
vendored
72
.github/workflows/maketplace-plugins-deploy.yml
vendored
|
|
@ -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<<EOF" >> $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})`
|
||||
});
|
||||
235
.github/workflows/render-preview-deploy.yml
vendored
235
.github/workflows/render-preview-deploy.yml
vendored
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
3.13.0
|
||||
3.14.0
|
||||
|
|
|
|||
114
cypress-tests/cypress-ee-platform.config.js
Normal file
114
cypress-tests/cypress-ee-platform.config.js
Normal file
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -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': {
|
||||
|
|
|
|||
189
cypress-tests/cypress.Dockerfile
Normal file
189
cypress-tests/cypress.Dockerfile
Normal file
|
|
@ -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"]
|
||||
|
|
@ -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.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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"]`;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export const airtableText = {
|
||||
airtable: "Airtable",
|
||||
cypressairtable: "cypress-Airtable",
|
||||
ApiKey: "Personal access token",
|
||||
apikeyPlaceholder: "**************",
|
||||
};
|
||||
airtable: "Airtable",
|
||||
cypressairtable: "cypress-Airtable",
|
||||
ApiKey: "Personal access token",
|
||||
apikeyPlaceholder: "**************",
|
||||
invalidAccessToken: "Authentication failed: Invalid personal access token",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
@ -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",
|
||||
|
|
@ -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`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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`);
|
||||
});
|
||||
});
|
||||
|
|
@ -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"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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')"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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.", () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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: `<?xml version="1.0" encoding="utf-8"?>
|
||||
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soap:Body>
|
||||
<ListOfContinentsByName xmlns="http://www.oorsprong.org/websamples.countryinfo">
|
||||
</ListOfContinentsByName>
|
||||
</soap:Body>
|
||||
</soap:Envelope>`,
|
||||
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,
|
||||
// },
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
31
docker/cloud/cloud-entrypoint.sh
Normal file
31
docker/cloud/cloud-entrypoint.sh
Normal file
|
|
@ -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 "$@"
|
||||
121
docker/cloud/cloud-production.Dockerfile
Normal file
121
docker/cloud/cloud-production.Dockerfile
Normal file
|
|
@ -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"]
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3.13.0
|
||||
3.14.0
|
||||
|
|
|
|||
3
frontend/assets/images/icons/editor/file-code.svg
Normal file
3
frontend/assets/images/icons/editor/file-code.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.02811 1.31381C2.22904 1.11288 2.50157 1 2.78572 1H7.07144C7.16616 1 7.257 1.03763 7.32397 1.10461L10.1812 3.96175C10.2481 4.02872 10.2857 4.11956 10.2857 4.21429V9.92857C10.2857 10.2127 10.1729 10.4853 9.97194 10.6862C9.77101 10.8871 9.49844 11 9.21429 11H2.78572C2.50156 11 2.22904 10.8871 2.02811 10.6862C1.82718 10.4853 1.71429 10.2127 1.71429 9.92857V2.07143C1.71429 1.78727 1.82718 1.51475 2.02811 1.31381ZM5.30739 5.26405C5.51659 5.47326 5.51659 5.81246 5.30739 6.02166L4.25762 7.07143L5.30739 8.12119C5.51659 8.33043 5.51659 8.66957 5.30739 8.87879C5.09818 9.088 4.75898 9.088 4.54977 8.87879L3.1212 7.45024C2.91199 7.24103 2.91199 6.90183 3.1212 6.69262L4.54977 5.26405C4.75898 5.05484 5.09818 5.05484 5.30739 5.26405ZM6.69263 6.02166C6.48342 5.81246 6.48342 5.47326 6.69263 5.26405C6.90184 5.05484 7.24104 5.05484 7.45024 5.26405L8.87879 6.69262C9.08801 6.90183 9.08801 7.24103 8.87879 7.45024L7.45024 8.87879C7.24104 9.088 6.90184 9.088 6.69263 8.87879C6.48342 8.66957 6.48342 8.33043 6.69263 8.12119L7.74239 7.07143L6.69263 6.02166Z" fill="#6A727C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
frontend/assets/images/icons/module-editor.svg
Normal file
3
frontend/assets/images/icons/module-editor.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.75815 0.483576C6.95842 0.283307 7.28312 0.283307 7.48338 0.483576L9.79108 2.79127C9.99135 2.99154 9.99135 3.31624 9.79108 3.5165L7.48338 5.8242C7.28312 6.02447 6.95842 6.02447 6.75815 5.8242L4.45046 3.5165C4.25018 3.31624 4.25018 2.99154 4.45046 2.79127L6.75815 0.483576ZM2.912 4.32973C3.11227 4.12946 3.43696 4.12946 3.63723 4.32973L5.94492 6.63743C6.1452 6.83769 6.1452 7.16239 5.94492 7.36266L3.63723 9.67035C3.43696 9.87063 3.11227 9.87063 2.912 9.67035L0.604304 7.36266C0.404034 7.16239 0.404034 6.83769 0.604304 6.63743L2.912 4.32973ZM11.3296 4.32973C11.1293 4.12946 10.8046 4.12946 10.6043 4.32973L8.29662 6.63743C8.09634 6.83769 8.09634 7.16239 8.29662 7.36266L10.6043 9.67035C10.8046 9.87063 11.1293 9.87063 11.3296 9.67035L13.6373 7.36266C13.8375 7.16239 13.8375 6.83769 13.6373 6.63743L11.3296 4.32973ZM7.48338 8.17589C7.28312 7.97561 6.95842 7.97561 6.75815 8.17589L4.45046 10.4835C4.25018 10.6838 4.25018 11.0086 4.45046 11.2089L6.75815 13.5166C6.95842 13.7168 7.28312 13.7168 7.48338 13.5166L9.79108 11.2089C9.99135 11.0086 9.99135 10.6838 9.79108 10.4835L7.48338 8.17589Z" fill="#1E823B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="25" viewBox="0 0 20 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.60152 6.16675C2.53311 6.16675 1.66699 7.03286 1.66699 8.10128C1.66699 8.5122 2.00011 8.84532 2.41104 8.84532C2.82197 8.84532 3.15509 8.5122 3.15509 8.10128C3.15509 7.85472 3.35496 7.65485 3.60152 7.65485C4.01245 7.65485 4.34557 7.32172 4.34557 6.9108C4.34557 6.49987 4.01245 6.16675 3.60152 6.16675ZM5.98248 6.16675C5.57155 6.16675 5.23843 6.49987 5.23843 6.9108C5.23843 7.32172 5.57155 7.65485 5.98248 7.65485H7.7682C8.17912 7.65485 8.51224 7.32172 8.51224 6.9108C8.51224 6.49987 8.17912 6.16675 7.7682 6.16675H5.98248ZM9.4051 6.9108C9.4051 6.49987 9.73822 6.16675 10.1492 6.16675C11.2176 6.16675 12.0837 7.03286 12.0837 8.10128C12.0837 8.5122 11.7506 8.84532 11.3396 8.84532C10.9287 8.84532 10.5956 8.5122 10.5956 8.10128C10.5956 7.85472 10.3957 7.65485 10.1492 7.65485C9.73822 7.65485 9.4051 7.32172 9.4051 6.9108ZM3.15509 10.4822C3.15509 10.0713 2.82197 9.73818 2.41104 9.73818C2.00011 9.73818 1.66699 10.0713 1.66699 10.4822V12.2679C1.66699 12.6789 2.00011 13.012 2.41104 13.012C2.82197 13.012 3.15509 12.6789 3.15509 12.2679V10.4822ZM2.41104 13.9049C2.82197 13.9049 3.15509 14.238 3.15509 14.6489C3.15509 14.8955 3.35496 15.0953 3.60152 15.0953C4.01245 15.0953 4.34557 15.4285 4.34557 15.8394C4.34557 16.2503 4.01245 16.5834 3.60152 16.5834C2.53311 16.5834 1.66699 15.7173 1.66699 14.6489C1.66699 14.238 2.00011 13.9049 2.41104 13.9049ZM5.23843 11.5239C5.23843 10.5377 6.03792 9.73818 7.02415 9.73818H13.5718C14.558 9.73818 15.3575 10.5377 15.3575 11.5239V14.9372L12.1665 14.1394C10.6407 13.758 9.25865 15.14 9.6401 16.6658L10.438 19.8572H7.02415C6.03792 19.8572 5.23843 19.0577 5.23843 18.0715V11.5239ZM18.1596 21.7151L17.2157 22.659C16.9832 22.8915 16.6063 22.8915 16.3739 22.659L14.4138 20.699L13.3715 21.7413C13.0443 22.0684 12.4853 21.9137 12.3731 21.4648L11.083 16.3043C10.974 15.8683 11.3689 15.4735 11.8048 15.5825L16.9654 16.8726C17.4142 16.9848 17.569 17.5438 17.2419 17.8709L16.1995 18.9133L18.1596 20.8733C18.392 21.1058 18.392 21.4827 18.1596 21.7151Z" fill="#CCD1D5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="25" viewBox="0 0 20 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.91058 8.08983C3.73357 8.08983 3.59007 8.23333 3.59007 8.41034V9.05136C3.59007 9.5824 3.15957 10.0129 2.62853 10.0129C2.09749 10.0129 1.66699 9.5824 1.66699 9.05136V8.41034C1.66699 7.17124 2.67148 6.16675 3.91058 6.16675H4.55161C5.08265 6.16675 5.51315 6.59724 5.51315 7.12829C5.51315 7.65933 5.08265 8.08983 4.55161 8.08983H3.91058ZM14.4875 7.12829C14.4875 6.59724 14.918 6.16675 15.449 6.16675H16.0901C17.3292 6.16675 18.3337 7.17124 18.3337 8.41034V9.05136C18.3337 9.5824 17.9031 10.0129 17.3721 10.0129C16.8411 10.0129 16.4106 9.5824 16.4106 9.05136V8.41034C16.4106 8.23333 16.2671 8.08983 16.0901 8.08983H15.449C14.918 8.08983 14.4875 7.65933 14.4875 7.12829ZM18.3337 19.9488C18.3337 19.4178 17.9031 18.9873 17.3721 18.9873C16.8411 18.9873 16.4106 19.4178 16.4106 19.9488V20.5898C16.4106 20.7669 16.2671 20.9103 16.0901 20.9103H15.449C14.918 20.9103 14.4875 21.3409 14.4875 21.8719C14.4875 22.4029 14.918 22.8334 15.449 22.8334H16.0901C17.3292 22.8334 18.3337 21.8289 18.3337 20.5898V19.9488ZM2.62853 18.9873C3.15957 18.9873 3.59007 19.4178 3.59007 19.9488V20.5898C3.59007 20.7669 3.73357 20.9103 3.91058 20.9103H4.55161C5.08265 20.9103 5.51315 21.3409 5.51315 21.8719C5.51315 22.4029 5.08265 22.8334 4.55161 22.8334H3.91058C2.67148 22.8334 1.66699 21.8289 1.66699 20.5898V19.9488C1.66699 19.4178 2.09749 18.9873 2.62853 18.9873ZM8.39776 20.9103C7.86672 20.9103 7.43622 21.3409 7.43622 21.8719C7.43622 22.4029 7.86672 22.8334 8.39776 22.8334H11.6029C12.1339 22.8334 12.5644 22.4029 12.5644 21.8719C12.5644 21.3409 12.1339 20.9103 11.6029 20.9103H8.39776ZM7.43622 7.12829C7.43622 6.59724 7.86672 6.16675 8.39776 6.16675H11.6029C12.1339 6.16675 12.5644 6.59724 12.5644 7.12829C12.5644 7.65933 12.1339 8.08983 11.6029 8.08983H8.39776C7.86672 8.08983 7.43622 7.65933 7.43622 7.12829ZM3.59007 12.8975C3.59007 12.3665 3.15957 11.936 2.62853 11.936C2.09749 11.936 1.66699 12.3665 1.66699 12.8975V16.1026C1.66699 16.6337 2.09749 17.0642 2.62853 17.0642C3.15957 17.0642 3.59007 16.6337 3.59007 16.1026V12.8975ZM17.3721 11.936C17.9031 11.936 18.3337 12.3665 18.3337 12.8975V16.1026C18.3337 16.6337 17.9031 17.0642 17.3721 17.0642C16.8411 17.0642 16.4106 16.6337 16.4106 16.1026V12.8975C16.4106 12.3665 16.8411 11.936 17.3721 11.936ZM7.43622 10.0123C6.37413 10.0123 5.51315 10.8733 5.51315 11.9354V17.0636C5.51315 18.1256 6.37413 18.9866 7.43622 18.9866H12.5644C13.6265 18.9866 14.4875 18.1256 14.4875 17.0636V11.9354C14.4875 10.8733 13.6265 10.0123 12.5644 10.0123H7.43622Z" fill="#CCD1D5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
3
frontend/assets/images/modules/blank-module.svg
Normal file
3
frontend/assets/images/modules/blank-module.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="103" height="42" viewBox="0 0 103 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.8298 0.809768C39.1863 -0.0104135 41.9933 -0.35598 44.1701 0.486052C45.5729 1.02867 46.4514 2.04627 46.896 3.25806C47.3183 4.40936 47.3337 5.68124 47.1897 6.83915C46.9018 9.15345 45.9098 11.5232 45.1884 12.8463C42.2889 18.1639 38.4247 22.5794 33.9787 26.2876C34.5399 26.5461 35.1642 26.7825 35.8578 26.9918C41.9402 28.8269 49.132 29.0238 56.4351 28.3262C63.7231 27.6301 71.0241 26.0534 77.2795 24.4159C82.4789 23.0549 88.4083 21.1217 93.2877 18.5872H89.6629V15.5872C92.9426 15.5872 96.5411 15.0656 99.4739 14.2229C99.907 14.0984 100.423 13.9818 100.902 14.0288C101.15 14.0531 101.586 14.1389 101.965 14.489C102.401 14.8919 102.519 15.4129 102.498 15.8141C102.48 16.1636 102.362 16.4455 102.281 16.6106C102.192 16.7928 102.085 16.9574 101.986 17.0947C101.788 17.3689 101.541 17.6478 101.3 17.9028C100.838 18.3892 100.271 18.9171 99.8104 19.3451L99.7374 19.4131C99.4924 19.6411 99.2855 19.8344 99.1255 19.9913C98.9808 20.1332 98.9314 20.1911 98.9302 20.1901C98.9301 20.19 98.9304 20.1894 98.931 20.1883C98.2154 21.2383 97.4441 22.6219 96.8553 24.0608C96.2546 25.5287 95.8916 26.9391 95.8916 28.062C95.8916 28.8904 95.22 29.562 94.3916 29.562C93.5632 29.562 92.8916 28.8904 92.8916 28.062C92.8916 26.3835 93.4101 24.5588 94.0788 22.9247C94.3303 22.3099 94.6099 21.7058 94.9046 21.1268C89.6711 23.8822 83.3845 25.9189 78.0392 27.3182C71.7006 28.9774 64.229 30.5954 56.7204 31.3126C49.227 32.0284 41.5983 31.8574 34.9912 29.8639C33.6317 29.4537 32.4359 28.9358 31.3973 28.3162C22.759 34.7179 12.321 38.8406 2.44859 41.9316C1.65801 42.1791 0.816456 41.7389 0.568925 40.9483C0.321395 40.1577 0.761626 39.3162 1.55221 39.0687C11.0603 36.0917 20.8465 32.2185 28.9544 26.3854C26.3865 23.6838 25.8234 20.144 26.3038 16.6476C26.8596 12.6029 28.8172 8.40718 30.9138 5.00092C32.0894 3.09103 34.4739 1.6298 36.8298 0.809768ZM31.3635 24.5523C35.8622 20.9286 39.7152 16.6174 42.5545 11.4101C43.1627 10.2948 43.9859 8.29206 44.2126 6.46889C44.3259 5.55807 44.2734 4.81998 44.0795 4.29123C43.9077 3.82297 43.6215 3.49045 43.0878 3.28402C41.8659 2.81135 39.8705 2.92794 37.816 3.64305C35.761 4.35831 34.126 5.50551 33.4687 6.57347C31.4736 9.81466 29.7509 13.5989 29.2759 17.056C28.8625 20.0646 29.3991 22.6539 31.3635 24.5523Z" fill="#ACB2B9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
35
frontend/package-lock.json
generated
35
frontend/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
></Route>
|
||||
<Route path="settings/*" element={<InstanceSettings {...this.props} />}></Route>
|
||||
<Route path="/:workspaceId/settings/*" element={<Settings {...this.props} />}></Route>
|
||||
<Route
|
||||
exact
|
||||
path="/:workspaceId/modules"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<HomePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} appType={'module'} />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{getAuditLogsRoutes(this.props)}
|
||||
<Route
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import EditorHeader from '@/AppBuilder/Header';
|
|||
import LeftSidebar from '@/AppBuilder/LeftSidebar';
|
||||
import Popups from './Popups';
|
||||
import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
// const EditorHeader = lazy(() => 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 (
|
||||
<div className={cx('wrapper', { editor: currentMode === 'edit' })}>
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<EditorHeader darkMode={darkMode} />
|
||||
<LeftSidebar switchDarkMode={changeToDarkMode} darkMode={darkMode} />
|
||||
</Suspense>
|
||||
{window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && <RealtimeCursors />}
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<ModuleProvider moduleId={moduleId}>
|
||||
<AppCanvas moduleId={moduleId} appId={appId} />
|
||||
<ModuleProvider moduleId={moduleId} appType={appType} isModuleMode={false} isModuleEditor={isModuleEditor}>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<EditorHeader darkMode={darkMode} />
|
||||
<LeftSidebar switchDarkMode={changeToDarkMode} darkMode={darkMode} />
|
||||
</Suspense>
|
||||
{window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && <RealtimeCursors />}
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<AppCanvas appId={appId} />
|
||||
<QueryPanel darkMode={darkMode} />
|
||||
<RightSideBar darkMode={darkMode} />
|
||||
</ModuleProvider>
|
||||
</DndProvider>
|
||||
<Popups darkMode={darkMode} />
|
||||
</DndProvider>
|
||||
<Popups darkMode={darkMode} />
|
||||
</ModuleProvider>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div
|
||||
|
|
@ -73,29 +132,14 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
|||
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}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
minWidth: `calc((100vw - 300px) - 48px)`,
|
||||
minWidth: isModuleMode ? '100%' : `calc((100vw - 300px) - 48px)`,
|
||||
}}
|
||||
className={`app-${appId} _tooljet-page-${getPageId()}`}
|
||||
>
|
||||
|
|
@ -107,7 +151,7 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
|||
{environmentLoadingState !== 'loading' && (
|
||||
<div>
|
||||
<Container
|
||||
id="canvas"
|
||||
id={moduleId}
|
||||
gridWidth={gridWidth}
|
||||
canvasWidth={canvasWidth}
|
||||
canvasHeight={canvasHeight}
|
||||
|
|
@ -115,8 +159,9 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
|||
canvasMaxWidth={canvasMaxWidth}
|
||||
isViewerSidebarPinned={isViewerSidebarPinned}
|
||||
pageSidebarStyle={pageSidebarStyle}
|
||||
appType={appType}
|
||||
/>
|
||||
<div id="component-portal" />
|
||||
{appType !== 'module' && <div id="component-portal" />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
<span
|
||||
style={{ cursor: 'pointer', marginLeft: '5px' }}
|
||||
onClick={() => {
|
||||
deleteComponents([id]);
|
||||
}}
|
||||
data-cy={`${componentName.toLowerCase()}-delete-button`}
|
||||
>
|
||||
<SolidIcon
|
||||
name="trash"
|
||||
width="12"
|
||||
height="12"
|
||||
fill={visibility === false ? 'var(--text-placeholder)' : '#fff'}
|
||||
/>
|
||||
</span>
|
||||
{!isModuleContainer && (
|
||||
<span
|
||||
style={{ cursor: 'pointer', marginLeft: '5px' }}
|
||||
onClick={() => {
|
||||
deleteComponents([id]);
|
||||
}}
|
||||
data-cy={`${componentName.toLowerCase()}-delete-button`}
|
||||
>
|
||||
<SolidIcon
|
||||
name="trash"
|
||||
width="12"
|
||||
height="12"
|
||||
fill={visibility === false ? 'var(--text-placeholder)' : '#fff'}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div style={styles}>
|
||||
{componentType === 'ModuleContainer' ? <ModuleContainerBlank /> : <NoComponentCanvasContainer />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
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(
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 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) && (
|
||||
<div style={{ display: showEmptyContainer ? 'block' : 'none' }}>
|
||||
<NoComponentCanvasContainer />
|
||||
</div>
|
||||
)}
|
||||
{renderEmptyContainer()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)}`,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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' && (
|
||||
<ConfigHandle
|
||||
id={id}
|
||||
widgetTop={layoutData.top}
|
||||
widgetHeight={layoutData.height}
|
||||
widgetTop={newLayoutData.top}
|
||||
widgetHeight={newLayoutData.height}
|
||||
showHandle={isWidgetActive}
|
||||
componentType={componentType}
|
||||
visibility={visibility}
|
||||
customClassName={isModuleContainer ? 'module-container' : ''}
|
||||
isModuleContainer={isModuleContainer}
|
||||
subContainerIndex={subContainerIndex}
|
||||
/>
|
||||
)}
|
||||
<RenderWidget
|
||||
id={id}
|
||||
componentType={componentType}
|
||||
widgetHeight={layoutData.height}
|
||||
widgetHeight={newLayoutData.height}
|
||||
widgetWidth={width}
|
||||
inCanvas={inCanvas}
|
||||
subContainerIndex={subContainerIndex}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,18 @@ export const SUBCONTAINER_CANVAS_BORDER_WIDTH = 1;
|
|||
|
||||
export const BOX_PADDING = 2;
|
||||
|
||||
export const DROPPABLE_PARENTS = new Set([
|
||||
'Calendar',
|
||||
'Kanban',
|
||||
'Form',
|
||||
'Tabs',
|
||||
'Modal',
|
||||
'ModalV2',
|
||||
'Listview',
|
||||
'Container',
|
||||
'Table',
|
||||
'ModuleContainer',
|
||||
]);
|
||||
export const TAB_CANVAS_PADDING = 7.5;
|
||||
|
||||
export const MODAL_CANVAS_PADDING = 5;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,14 @@ export function snapToGrid(canvasWidth, x, y) {
|
|||
}
|
||||
|
||||
//TODO: componentTypes should be a key value pair and get the definition directly by passing the componentType
|
||||
export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, currentLayout, realCanvasRef, parentId) => {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// }
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
100
frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx
Normal file
100
frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx
Normal file
|
|
@ -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 (
|
||||
<components.Option {...props}>
|
||||
<div className="d-flex justify-content-between">
|
||||
<span>{props.label}</span>
|
||||
{props.isSelected && (
|
||||
<span>
|
||||
<Check width={'20'} fill={'#3E63DD'} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div
|
||||
className="row fx-container"
|
||||
data-cy={`dropdown-${meta.displayName ? String(meta.displayName).toLowerCase().replace(/\s+/g, '-') : 'common'}`}
|
||||
>
|
||||
<div className="field" onClick={(e) => e.stopPropagation()}>
|
||||
<SelectComponent
|
||||
options={options}
|
||||
value={value}
|
||||
hasSearch={true}
|
||||
onChange={onValueChange}
|
||||
width={224}
|
||||
height={32}
|
||||
styles={selectCustomStyles}
|
||||
useCustomStyles={true}
|
||||
classNamePrefix="inspector-select"
|
||||
components={{
|
||||
IndicatorSeparator: () => null,
|
||||
Option,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -20,4 +20,5 @@ export const TypeMapping = {
|
|||
visibility: 'Visibility',
|
||||
numberInput: 'NumberInput',
|
||||
tableRowHeightInput: 'TableRowHeightInput',
|
||||
query: 'Query',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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.*\}\}$/;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div
|
||||
className={`col-auto pt-0 fx-common fx-button-container ${(isEventManagerParam || codeShow) && 'show-fx-button-container'
|
||||
}`}
|
||||
>
|
||||
<FxButton
|
||||
active={codeShow}
|
||||
onPress={() => {
|
||||
if (codeShow) {
|
||||
setForceCodeBox(false);
|
||||
onFxPress(false);
|
||||
} else {
|
||||
setForceCodeBox(true);
|
||||
onFxPress(true);
|
||||
}
|
||||
}}
|
||||
dataCy={cyLabel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const fxClass = isEventManagerParam ? 'justify-content-start' : 'justify-content-end';
|
||||
return (
|
||||
<div className={cx({ 'codeShow-active': codeShow }, 'wrapper-div-code-editor')}>
|
||||
|
|
@ -538,26 +566,7 @@ const DynamicEditorBridge = (props) => {
|
|||
)}
|
||||
<div className={`${(paramType ?? 'code') === 'code' ? 'd-none' : ''} flex-grow-1`}>
|
||||
<div style={{ marginBottom: codeShow ? '0.5rem' : '0px' }} className={`d-flex align-items-center ${fxClass}`}>
|
||||
{paramLabel !== 'Type' && isFxNotRequired === undefined && (
|
||||
<div
|
||||
className={`col-auto pt-0 fx-common fx-button-container ${(isEventManagerParam || codeShow) && 'show-fx-button-container'
|
||||
}`}
|
||||
>
|
||||
<FxButton
|
||||
active={codeShow}
|
||||
onPress={() => {
|
||||
if (codeShow) {
|
||||
setForceCodeBox(false);
|
||||
onFxPress(false);
|
||||
} else {
|
||||
setForceCodeBox(true);
|
||||
onFxPress(true);
|
||||
}
|
||||
}}
|
||||
dataCy={cyLabel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{renderFx()}
|
||||
</div>
|
||||
</div>
|
||||
{!codeShow && (
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
|||
}}
|
||||
>
|
||||
<div className="global-settings-app-wrapper p-0 m-0 ">
|
||||
<EditAppName />
|
||||
<div className="d-flex flex-row">
|
||||
{isModuleEditor && <ModuleEditorBanner />}
|
||||
<EditAppName />
|
||||
</div>
|
||||
</div>
|
||||
<HeaderActions darkMode={darkMode} />
|
||||
<div className="d-flex align-items-center">
|
||||
|
|
@ -96,20 +102,21 @@ export const EditorHeader = ({ darkMode }) => {
|
|||
{shouldEnableMultiplayer && <UpdatePresenceMultiPlayer />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="navbar-seperator"></div>
|
||||
{/* <div className="d-flex align-items-center p-0" style={{ marginRight: '12px' }}></div> */}
|
||||
{!isModuleEditor && <div className="navbar-seperator"></div>}
|
||||
</div>
|
||||
<div className="d-flex align-items-center p-0">
|
||||
<div className="d-flex version-manager-container p-0 mx-2 align-items-center ">
|
||||
{/* {editingVersion && ( */}
|
||||
<AppEnvironments darkMode={darkMode} />
|
||||
{/* )} */}
|
||||
<AppVersionsManager darkMode={darkMode} />
|
||||
<GitSyncManager />
|
||||
{!isModuleEditor && (
|
||||
<>
|
||||
<AppEnvironments darkMode={darkMode} />
|
||||
<AppVersionsManager darkMode={darkMode} />
|
||||
<GitSyncManager />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RightTopHeaderButtons />
|
||||
<RightTopHeaderButtons isModuleEditor={isModuleEditor} />
|
||||
<BuildSuggestions />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="d-flex justify-content-end navbar-right-section" style={{ width: '300px', paddingRight: '12px' }}>
|
||||
<div className=" release-buttons navbar-nav flex-row">
|
||||
<PreviewAndShareIcons />
|
||||
<PromoteReleaseButton />
|
||||
{!isModuleEditor && <PromoteReleaseButton />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('inspect')}
|
||||
darkMode={darkMode}
|
||||
icon="inspect"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`}
|
||||
tip="Inspector"
|
||||
ref={setSideBarBtnRefs('inspect')}
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
icon="debugger"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => 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'),
|
||||
})}
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('page')}
|
||||
darkMode={darkMode}
|
||||
icon="page"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`}
|
||||
tip="Pages"
|
||||
ref={setSideBarBtnRefs('page')}
|
||||
/>
|
||||
{renderCommonItems()}
|
||||
<SidebarItem
|
||||
icon="settings"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => handleSelectedSidebarItem('settings')}
|
||||
className={`left-sidebar-item left-sidebar-layout`}
|
||||
badge={true}
|
||||
tip="Settings"
|
||||
ref={setSideBarBtnRefs('settings')}
|
||||
isModuleEditor={isModuleEditor}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx('left-sidebar', { 'dark-theme theme-dark': darkMode })} data-cy="left-sidebar-inspector">
|
||||
{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'),
|
||||
})}
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('page')}
|
||||
darkMode={darkMode}
|
||||
icon="page"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`}
|
||||
tip="Pages"
|
||||
ref={setSideBarBtnRefs('page')}
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('inspect')}
|
||||
darkMode={darkMode}
|
||||
icon="inspect"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`}
|
||||
tip="Inspector"
|
||||
ref={setSideBarBtnRefs('inspect')}
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
icon="debugger"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => handleSelectedSidebarItem('debugger')}
|
||||
className={`left-sidebar-item left-sidebar-layout`}
|
||||
badge={true}
|
||||
count={unreadErrorCount}
|
||||
tip="Debugger"
|
||||
ref={setSideBarBtnRefs('debugger')}
|
||||
/>
|
||||
<SidebarItem
|
||||
icon="settings"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => handleSelectedSidebarItem('settings')}
|
||||
className={`left-sidebar-item left-sidebar-layout`}
|
||||
badge={true}
|
||||
tip="Settings"
|
||||
ref={setSideBarBtnRefs('settings')}
|
||||
/>
|
||||
|
||||
{/* {dataSources?.length > 0 && (
|
||||
<LeftSidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('datasource')}
|
||||
icon="datasource"
|
||||
className={`left-sidebar-item left-sidebar-layout sidebar-datasources`}
|
||||
tip="Sources"
|
||||
ref={setSideBarBtnRefs('datasource')}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
{renderLeftSidebarItems()}
|
||||
<Popover
|
||||
onInteractOutside={(e) => {
|
||||
// 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}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue