mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Merge branch 'appbuilder/sprint-11' into feat/steps-v2-alignment-style-improvement
This commit is contained in:
commit
ad70e31df3
128 changed files with 2207 additions and 461 deletions
118
.github/workflows/cypress-platform.yml
vendored
118
.github/workflows/cypress-platform.yml
vendored
|
|
@ -147,13 +147,27 @@ jobs:
|
|||
name: screenshots-${{ matrix.edition }}
|
||||
path: cypress-tests/cypress/screenshots
|
||||
|
||||
|
||||
Cypress-Platform-Subpath:
|
||||
runs-on: ubuntu-22.04
|
||||
if: |
|
||||
github.event.action == 'labeled' &&
|
||||
(github.event.label.name == 'run-cypress-platform-subpath' ||
|
||||
github.event.label.name == 'run-proxy-platform')
|
||||
github.event.action == 'labeled' &&
|
||||
(
|
||||
github.event.label.name == 'run-cypress-platform-subpath' ||
|
||||
github.event.label.name == 'run-proxy-platform' ||
|
||||
github.event.label.name == 'run-ce-cypress-platform-subpath' ||
|
||||
github.event.label.name == 'run-ee-cypress-platform-subpath'
|
||||
)
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
edition: >-
|
||||
${{
|
||||
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-subpath') && fromJson('["ce", "ee"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-proxy-platform') && fromJson('["ce", "ee"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform-subpath') && fromJson('["ce"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform-subpath') && fromJson('["ee"]') ||
|
||||
fromJson('[]')
|
||||
}}
|
||||
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
|
|
@ -161,11 +175,22 @@ jobs:
|
|||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Checkout
|
||||
- name: Set up Git authentication for private submodules
|
||||
run: |
|
||||
git config --global url."https://x-access-token:${{ secrets.CUSTOM_GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- name: Checkout with Submodules
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: Checking out the correct branch for submodules EE
|
||||
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 configuration
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
|
|
@ -186,13 +211,14 @@ jobs:
|
|||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/production.Dockerfile
|
||||
file: ${{ matrix.edition == 'ee' && 'docker/ee/ee-production.Dockerfile' || 'docker/ce-production.Dockerfile' }}
|
||||
push: true
|
||||
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up environment variables
|
||||
run: |
|
||||
echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'ee' || 'ce' }}" >> .env
|
||||
echo "TOOLJET_HOST=http://localhost:3000" >> .env
|
||||
echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env
|
||||
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env
|
||||
|
|
@ -254,15 +280,30 @@ jobs:
|
|||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: screenshots
|
||||
name: screenshots-${{ matrix.edition }}
|
||||
path: cypress-tests/cypress/screenshots
|
||||
|
||||
Cypress-Platform-Proxy:
|
||||
runs-on: ubuntu-22.04
|
||||
if: |
|
||||
github.event.action == 'labeled' &&
|
||||
(github.event.label.name == 'run-cypress-platform-proxy' ||
|
||||
github.event.label.name == 'run-proxy-platform')
|
||||
github.event.action == 'labeled' &&
|
||||
(
|
||||
github.event.label.name == 'run-cypress-platform-proxy' ||
|
||||
github.event.label.name == 'run-proxy-platform' ||
|
||||
github.event.label.name == 'run-ce-cypress-platform-proxy' ||
|
||||
github.event.label.name == 'run-ee-cypress-platform-proxy'
|
||||
)
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
edition: >-
|
||||
${{
|
||||
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-proxy') && fromJson('["ce", "ee"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-proxy-platform') && fromJson('["ce", "ee"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform-proxy') && fromJson('["ce"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform-proxy') && fromJson('["ee"]') ||
|
||||
fromJson('[]')
|
||||
}}
|
||||
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
|
|
@ -270,11 +311,22 @@ jobs:
|
|||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Checkout
|
||||
- name: Set up Git authentication for private submodules
|
||||
run: |
|
||||
git config --global url."https://x-access-token:${{ secrets.CUSTOM_GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- name: Checkout with Submodules
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: Checking out the correct branch for submodules EE
|
||||
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 configuration
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
|
|
@ -295,13 +347,14 @@ jobs:
|
|||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/production.Dockerfile
|
||||
file: ${{ matrix.edition == 'ee' && 'docker/ee/ee-production.Dockerfile' || 'docker/ce-production.Dockerfile' }}
|
||||
push: true
|
||||
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up environment variables
|
||||
run: |
|
||||
echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'ee' || 'ce' }}" >> .env
|
||||
echo "TOOLJET_HOST=http://localhost:3000" >> .env
|
||||
echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env
|
||||
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env
|
||||
|
|
@ -375,15 +428,30 @@ jobs:
|
|||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: screenshots
|
||||
name: screenshots-${{ matrix.edition }}
|
||||
path: cypress-tests/cypress/screenshots
|
||||
|
||||
Cypress-Platform-Proxy-Subpath:
|
||||
runs-on: ubuntu-22.04
|
||||
if: |
|
||||
github.event.action == 'labeled' &&
|
||||
(github.event.label.name == 'run-cypress-platform-proxy-subpath' ||
|
||||
github.event.label.name == 'run-proxy-platform')
|
||||
github.event.action == 'labeled' &&
|
||||
(
|
||||
github.event.label.name == 'run-cypress-platform-proxy-subpath' ||
|
||||
github.event.label.name == 'run-proxy-platform' ||
|
||||
github.event.label.name == 'run-ce-cypress-platform-proxy-subpath' ||
|
||||
github.event.label.name == 'run-ee-cypress-platform-proxy-subpath'
|
||||
)
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
edition: >-
|
||||
${{
|
||||
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-proxy-subpath') && fromJson('["ce", "ee"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-proxy-platform') && fromJson('["ce", "ee"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform-proxy-subpath') && fromJson('["ce"]') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform-proxy-subpath') && fromJson('["ee"]') ||
|
||||
fromJson('[]')
|
||||
}}
|
||||
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
|
|
@ -391,11 +459,22 @@ jobs:
|
|||
with:
|
||||
node-version: 18.18.2
|
||||
|
||||
- name: Checkout
|
||||
- name: Set up Git authentication for private submodules
|
||||
run: |
|
||||
git config --global url."https://x-access-token:${{ secrets.CUSTOM_GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/"
|
||||
|
||||
- name: Checkout with Submodules
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: Checking out the correct branch for submodules EE
|
||||
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 configuration
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
|
|
@ -416,13 +495,14 @@ jobs:
|
|||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/production.Dockerfile
|
||||
file: ${{ matrix.edition == 'ee' && 'docker/ee/ee-production.Dockerfile' || 'docker/ce-production.Dockerfile' }}
|
||||
push: true
|
||||
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up environment variables
|
||||
run: |
|
||||
echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'ee' || 'ce' }}" >> .env
|
||||
echo "TOOLJET_HOST=http://localhost:3000" >> .env
|
||||
echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env
|
||||
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env
|
||||
|
|
@ -497,5 +577,5 @@ jobs:
|
|||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: screenshots
|
||||
name: screenshots-${{ matrix.edition }}
|
||||
path: cypress-tests/cypress/screenshots
|
||||
|
|
|
|||
100
.github/workflows/docker-release.yml
vendored
100
.github/workflows/docker-release.yml
vendored
|
|
@ -137,7 +137,8 @@ jobs:
|
|||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
args: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||
build-args: |
|
||||
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||
file: docker/ee/ee-production.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }}
|
||||
|
|
@ -152,8 +153,9 @@ jobs:
|
|||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
args: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||
file: docker/ee-production.Dockerfile
|
||||
build-args: |
|
||||
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}
|
||||
file: docker/ee/ee-production.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }}
|
||||
platforms: linux/amd64
|
||||
|
|
@ -230,3 +232,95 @@ jobs:
|
|||
# fi
|
||||
|
||||
# curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
|
||||
try-tooljet-image-build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-tooljet-image-for-ee-edtion
|
||||
if: ${{ needs.build-tooljet-image-for-ee-edtion.result == 'success' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code to develop
|
||||
if: "!contains(github.event.release.tag_name, 'ee-lts')"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: refs/heads/main
|
||||
|
||||
- name: Checkout code to lts-3.0
|
||||
if: contains(github.event.release.tag_name, '-ee-lts')
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: refs/heads/lts-3.0
|
||||
|
||||
# Create Docker Buildx builder with platform configuration
|
||||
- name: Set up Docker Buildx
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
|
||||
docker buildx use mybuilder
|
||||
|
||||
- name: Set DOCKER_CLI_EXPERIMENTAL
|
||||
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
|
||||
|
||||
- name: use mybuilder buildx
|
||||
run: docker buildx use mybuilder
|
||||
|
||||
- name: Docker Login
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Check if Docker image is present
|
||||
id: check-image-presence
|
||||
run: |
|
||||
response=$(curl -s "https://hub.docker.com/v2/repositories/tooljet/tooljet/tags/${{ github.event.release.tag_name }}")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to fetch JSON response. Stopping workflow execution."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $response == *"tag '${{ github.event.release.tag_name }}' not found"* ]]; then
|
||||
echo "Docker image tag '${{ github.event.release.tag_name }}' not present."
|
||||
exit 1
|
||||
else
|
||||
echo "Docker image tag '${{ github.event.release.tag_name }}' is present."
|
||||
fi
|
||||
|
||||
- name: Build and Push Docker image for non-EE-LTS
|
||||
if: "!contains(github.event.release.tag_name, '-ee-lts')"
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/ee/ee-try-tooljet.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/try:${{ github.event.release.tag_name }},tooljet/try:ee-latest
|
||||
platforms: linux/amd64
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and Push Docker image for EE-LTS-3.0
|
||||
if: contains(github.event.release.tag_name, '-ee-lts')
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/ee/ee-try-tooljet-lts.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/try:${{ github.event.release.tag_name }},tooljet/try:ee-lts-latest
|
||||
platforms: linux/amd64
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Send Slack Notification
|
||||
run: |
|
||||
if [[ "${{ job.status }}" == "success" ]]; then
|
||||
message="Try-ToolJet image published:\\n\`tooljet/try:${{ github.event.release.tag_name }}\`"
|
||||
else
|
||||
message="Job '${{ env.JOB_NAME }}' failed! tooljet/try:${{ github.event.release.tag_name }}"
|
||||
fi
|
||||
|
||||
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
|
|
|||
44
.github/workflows/render-suspend-labeler.yml
vendored
44
.github/workflows/render-suspend-labeler.yml
vendored
|
|
@ -8,16 +8,16 @@ permissions:
|
|||
issues: write
|
||||
|
||||
jobs:
|
||||
label-stale-deploys:
|
||||
label-stale-ce-deploys:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: akshaysasidrn/stale-label-fetch@v1.1
|
||||
id: stale-label
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-label: 'active-review-app'
|
||||
stale-label: 'active-ce-review-app'
|
||||
stale-time: '86400'
|
||||
type: 'pull_request'
|
||||
- name: Get stale numbers
|
||||
|
|
@ -40,6 +40,42 @@ jobs:
|
|||
issue_number: prNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['suspend-review-app']
|
||||
labels: ['suspend-ce-review-app']
|
||||
})
|
||||
}
|
||||
|
||||
label-stale-ee-deploys:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: akshaysasidrn/stale-label-fetch@v1.1
|
||||
id: stale-label
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-label: 'active-ee-review-app'
|
||||
stale-time: '86400'
|
||||
type: 'pull_request'
|
||||
- name: Get stale numbers
|
||||
run: echo "Matched PR numbers - ${{ steps.stale-label.outputs.stale-numbers }}"
|
||||
- name: Add suspend label
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
STALE_NUMBERS: ${{ steps.stale-label.outputs.stale-numbers }}
|
||||
with:
|
||||
github-token: ${{ secrets.TJ_BOT_PAT }}
|
||||
script: |
|
||||
if (!process.env.STALE_NUMBERS) return
|
||||
|
||||
const prNumbers = process.env.STALE_NUMBERS.split(",")
|
||||
|
||||
console.log(`Adding suspend labels for: ${prNumbers}`)
|
||||
|
||||
for (const prNumber of prNumbers) {
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: prNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['suspend-ee-review-app']
|
||||
})
|
||||
}
|
||||
|
|
|
|||
2
.github/workflows/vulnerability-ci.yml
vendored
2
.github/workflows/vulnerability-ci.yml
vendored
|
|
@ -11,7 +11,7 @@ on:
|
|||
# Schedule the workflow to run every two weeks once
|
||||
|
||||
schedule:
|
||||
- cron: '30 5 */14 * *'
|
||||
- cron: '30 5 * * 1'
|
||||
|
||||
jobs:
|
||||
PeriodicVulnerability-CheckOn-frontend-code:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export const postgreSqlText = {
|
|||
allDataSources: () => {
|
||||
return Cypress.env("marketplace_action")
|
||||
? "All data sources (44)"
|
||||
: "All data sources (42)";
|
||||
: "All data sources (43)";
|
||||
},
|
||||
commonlyUsed: "Commonly used (5)",
|
||||
allDatabase: () => {
|
||||
|
|
@ -13,7 +13,7 @@ export const postgreSqlText = {
|
|||
? "Databases (20)"
|
||||
: "Databases (18)";
|
||||
},
|
||||
allApis: "APIs (20)",
|
||||
allApis: "APIs (21)",
|
||||
allCloudStorage: "Cloud Storages (4)",
|
||||
|
||||
postgreSQL: "PostgreSQL",
|
||||
|
|
|
|||
|
|
@ -43,8 +43,10 @@ describe("Editor title", () => {
|
|||
cy.apiDeleteApp();
|
||||
});
|
||||
it("should verify titles", () => {
|
||||
cy.url().should("include", "/my-workspace");
|
||||
cy.title().should("eq", "Dashboard | ToolJet");
|
||||
cy.url().should("include", "/tjs-workspace");
|
||||
// cy.title().should("eq", "Dashboard | ToolJet");
|
||||
cy.title().should("eq", "ToolJet");
|
||||
|
||||
cy.log(data.appName);
|
||||
|
||||
cy.openApp();
|
||||
|
|
@ -54,12 +56,20 @@ describe("Editor title", () => {
|
|||
cy.openInCurrentTab(commonWidgetSelector.previewButton);
|
||||
|
||||
cy.url().should("include", `/applications/${Cypress.env("appId")}`);
|
||||
cy.title().should("eq", `Preview - ${data.appName} | ToolJet`);
|
||||
cy.title().should("eq", `${data.appName} | ToolJet`);
|
||||
// cy.title().should("eq", `Preview - ${data.appName} | ToolJet`);
|
||||
|
||||
cy.go("back");
|
||||
cy.releaseApp();
|
||||
cy.url().then((url) => cy.visit(`/applications/${url.split("/").pop()}`));
|
||||
cy.url().then((url) => {
|
||||
const appId = url.split("/").filter(Boolean).pop();
|
||||
cy.log(appId);
|
||||
cy.visit(`/applications/${appId}`);
|
||||
});
|
||||
|
||||
cy.url().should("include", `/applications/${Cypress.env("appId")}`);
|
||||
cy.title().should("eq", `${data.appName}`);
|
||||
cy.title().should("eq", `${data.appName} | ToolJet`);
|
||||
// cy.title().should("eq", `${data.appName}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -35,13 +35,13 @@ describe("Editor- Global Settings", () => {
|
|||
"have.text",
|
||||
"Global settings"
|
||||
);
|
||||
cy.get(
|
||||
'[data-cy="label-hide-header-for-launched-apps"]'
|
||||
).verifyVisibleElement("have.text", "Hide header for launched apps");
|
||||
cy.get('[data-cy="label-maintenance-mode"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
"Maintenance mode"
|
||||
);
|
||||
// cy.get(
|
||||
// '[data-cy="label-hide-header-for-launched-apps"]'
|
||||
// ).verifyVisibleElement("have.text", "Hide header for launched apps");
|
||||
// cy.get('[data-cy="label-maintenance-mode"]').verifyVisibleElement(
|
||||
// "have.text",
|
||||
// "Maintenance mode"
|
||||
// );
|
||||
cy.hideTooltip();
|
||||
cy.get('[data-cy="label-max-canvas-width"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -60,7 +60,7 @@ describe("Editor- Global Settings", () => {
|
|||
);
|
||||
|
||||
verifyWidgetColorCss(
|
||||
".canvas-area",
|
||||
'[data-cy="real-canvas"]',
|
||||
"background-color",
|
||||
data.backgroundColor,
|
||||
true
|
||||
|
|
@ -87,24 +87,25 @@ describe("Editor- Global Settings", () => {
|
|||
cy.get("[data-cy='left-sidebar-settings-button']").click();
|
||||
cy.get('[data-cy="toggle-maintenance-mode"]').realClick();
|
||||
cy.get('[data-cy="modal-confirm-button"]').click();
|
||||
cy.verifyToastMessage(
|
||||
commonSelectors.toastMessage,
|
||||
"Application is on maintenance.",
|
||||
false
|
||||
);
|
||||
// cy.verifyToastMessage(
|
||||
// commonSelectors.toastMessage,
|
||||
// "Application is on maintenance.",
|
||||
// false
|
||||
// );
|
||||
cy.forceClickOnCanvas();
|
||||
cy.wait(500);
|
||||
cy.waitForAutoSave();
|
||||
//Fix this after the release. 2.9.0
|
||||
// cy.get('[data-cy="button-release"]').click();
|
||||
// cy.get('[data-cy="yes-button"]').click();
|
||||
// cy.get('[data-cy="editor-page-logo"]').click();
|
||||
// cy.get(`[data-cy="${data.appName.toLowerCase()}-card"]`)
|
||||
// .realHover()
|
||||
// .find('[data-cy="launch-button"]')
|
||||
// .invoke("attr", "class")
|
||||
// .should("contains", "disabled-btn");
|
||||
|
||||
// Fix this after the release. 2.9.0
|
||||
cy.get('[data-cy="button-release"]').click();
|
||||
cy.get('[data-cy="yes-button"]').click();
|
||||
cy.get('[data-cy="editor-page-logo"]').click();
|
||||
cy.get('[data-cy="back-to-app-option"]').click();
|
||||
cy.get(`[data-cy="${data.appName.toLowerCase()}-card"]`)
|
||||
.realHover().within(() => {
|
||||
cy.get('[data-cy="launch-button"]').should('have.text', 'Maintenance')
|
||||
.invoke("attr", "class")
|
||||
.should("contains", "disabled-btn");
|
||||
})
|
||||
cy.apiDeleteApp();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { postgreSqlText } from "Texts/postgreSql";
|
|||
import { bigqueryText } from "Texts/bigquery";
|
||||
import { firestoreText } from "Texts/firestore";
|
||||
import { commonSelectors } from "Selectors/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
fillDataSourceTextField,
|
||||
selectAndAddDataSource,
|
||||
|
|
@ -16,6 +17,7 @@ const data = {};
|
|||
describe("Data source BigQuery", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
cy.intercept("GET", "/api/v2/data_sources");
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
|
|
@ -50,10 +52,19 @@ describe("Data source BigQuery", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource(
|
||||
"databases",
|
||||
bigqueryText.bigQuery,
|
||||
data.dataSourceName
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-bigquery`,
|
||||
"bigquery",
|
||||
[{ key: "private_key", value: "", encrypted: true }]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-bigquery-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-bigquery`
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-private-key"]').verifyVisibleElement(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { fake } from "Fixtures/fake";
|
||||
import { postgreSqlSelector } from "Selectors/postgreSql";
|
||||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonWidgetText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
|
||||
import {
|
||||
addQuery,
|
||||
|
|
@ -21,6 +20,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -51,13 +51,20 @@ describe("Data sources", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("databases", "ClickHouse", data.dataSourceName);
|
||||
|
||||
// cy.get(postgreSqlSelector.dataSourceNameInputField).should(
|
||||
// //username,password,host,port,protocol,dbname,usepost, trimquery,gzip,debug,raw
|
||||
// "have.value",
|
||||
// "ClickHouse"
|
||||
// );
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-clickhouse`,
|
||||
"clickhouse",
|
||||
[]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-clickhouse-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-clickhouse`
|
||||
);
|
||||
cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.labelUserName
|
||||
|
|
@ -78,7 +85,7 @@ describe("Data sources", () => {
|
|||
|
||||
cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.labelDbName
|
||||
"Database Name"
|
||||
);
|
||||
cy.get('[data-cy="label-protocol"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -140,11 +147,7 @@ describe("Data sources", () => {
|
|||
Cypress.env("pg_host")
|
||||
);
|
||||
fillDataSourceTextField(postgreSqlText.labelPort, "8123", "8123");
|
||||
fillDataSourceTextField(
|
||||
postgreSqlText.labelDbName,
|
||||
"database name",
|
||||
"{del}"
|
||||
);
|
||||
fillDataSourceTextField("Database Name", "database name", "{del}");
|
||||
fillDataSourceTextField(
|
||||
postgreSqlText.labelUserName,
|
||||
postgreSqlText.placeholderEnterUserName,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { fake } from "Fixtures/fake";
|
||||
import { postgreSqlSelector } from "Selectors/postgreSql";
|
||||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonWidgetText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
|
||||
import {
|
||||
addQuery,
|
||||
|
|
@ -21,6 +20,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -50,7 +50,23 @@ describe("Data sources", () => {
|
|||
"have.text",
|
||||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
selectAndAddDataSource("databases", "CosmosDB", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-cosmosdb`,
|
||||
"cosmosdb",
|
||||
[
|
||||
{ key: "endpoint", value: "" },
|
||||
{ key: "key", value: "", encrypted: true },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-cosmosdb-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-cosmosdb`
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-end-point"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -92,7 +108,7 @@ describe("Data sources", () => {
|
|||
deleteDatasource(`cypress-${data.dataSourceName}-cosmosdb`);
|
||||
});
|
||||
|
||||
it.only("Should verify the functionality of CosmosDB connection form.", () => {
|
||||
it("Should verify the functionality of CosmosDB connection form.", () => {
|
||||
selectAndAddDataSource("databases", "CosmosDB", data.dataSourceName);
|
||||
|
||||
fillDataSourceTextField(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonWidgetText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
|
||||
|
||||
import {
|
||||
|
|
@ -22,6 +22,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -52,7 +53,27 @@ describe("Data sources", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("databases", "CouchDB", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-couchdb`,
|
||||
"couchdb",
|
||||
[
|
||||
{ key: "username", value: "", encrypted: false },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
{ key: "database", value: "" },
|
||||
{ key: "port", value: "5984" },
|
||||
{ key: "host", value: "" },
|
||||
{ key: "protocol" },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-couchdb-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-couchdb`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -72,7 +93,7 @@ describe("Data sources", () => {
|
|||
);
|
||||
cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.labelDbName
|
||||
"Database Name"
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-protocol"]').verifyVisibleElement(
|
||||
|
|
@ -122,11 +143,7 @@ describe("Data sources", () => {
|
|||
Cypress.env("couchdb_host")
|
||||
);
|
||||
fillDataSourceTextField(postgreSqlText.labelPort, "5984 ", "5984");
|
||||
fillDataSourceTextField(
|
||||
postgreSqlText.labelDbName,
|
||||
"database name",
|
||||
"{del}"
|
||||
);
|
||||
fillDataSourceTextField("Database Name", "database name", "{del}");
|
||||
fillDataSourceTextField(
|
||||
postgreSqlText.labelUserName,
|
||||
"username for couchDB",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { dynamoDbText } from "Texts/dynamodb";
|
||||
import { commonSelectors } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
|
||||
import {
|
||||
fillDataSourceTextField,
|
||||
|
|
@ -20,6 +20,7 @@ const data = {};
|
|||
describe("Data source DynamoDB", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -50,10 +51,28 @@ describe("Data source DynamoDB", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource(
|
||||
"databases",
|
||||
dynamoDbText.dynamoDb,
|
||||
data.dataSourceName
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-dynamodb`,
|
||||
"dynamodb",
|
||||
[
|
||||
{ key: "region", value: "" },
|
||||
{ key: "access_key", value: "" },
|
||||
{ key: "secret_key", value: "", encrypted: true },
|
||||
{
|
||||
key: "instance_metadata_credentials",
|
||||
value: "iam_access_keys",
|
||||
encrypted: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-dynamodb-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-dynamodb`
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-region"]').verifyVisibleElement(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { elasticsearchText } from "Texts/elasticsearch";
|
||||
import { commonSelectors } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
fillDataSourceTextField,
|
||||
selectAndAddDataSource,
|
||||
|
|
@ -18,6 +18,7 @@ const data = {};
|
|||
describe("Data source Elasticsearch", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
|
||||
});
|
||||
|
||||
|
|
@ -46,12 +47,27 @@ describe("Data source Elasticsearch", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource(
|
||||
"databases",
|
||||
elasticsearchText.elasticSearch,
|
||||
data.lastName
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-elasticsearch`,
|
||||
"elasticsearch",
|
||||
[
|
||||
{ key: "host", value: "localhost" },
|
||||
{ key: "port", value: 9200 },
|
||||
{ key: "username", value: "" },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
{ key: "ssl_enabled", value: true, encrypted: false },
|
||||
{ key: "ssl_certificate", value: "none", encrypted: false },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-elasticsearch-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-elasticsearch`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.labelHost
|
||||
|
|
@ -74,7 +90,7 @@ describe("Data source Elasticsearch", () => {
|
|||
);
|
||||
cy.get(postgreSqlSelector.labelSSLCertificate).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.sslCertificate
|
||||
"SSL Certificate"
|
||||
);
|
||||
cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { firestoreText } from "Texts/firestore";
|
||||
import { commonSelectors } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
verifyCouldnotConnectWithAlert,
|
||||
deleteDatasource,
|
||||
|
|
@ -18,6 +18,7 @@ const data = {};
|
|||
describe("Data source Firestore", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -47,12 +48,20 @@ describe("Data source Firestore", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource(
|
||||
"databases",
|
||||
firestoreText.firestore,
|
||||
data.dataSourceName
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-firestore`,
|
||||
"firestore",
|
||||
[{ key: "gcp_key", value: "", encrypted: true }]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-firestore-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-firestore`
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-private-key"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
firestoreText.labelPrivateKey
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { fake } from "Fixtures/fake";
|
||||
import { postgreSqlSelector } from "Selectors/postgreSql";
|
||||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonWidgetText, commonText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
addQuery,
|
||||
fillDataSourceTextField,
|
||||
|
|
@ -24,6 +24,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -54,7 +55,25 @@ describe("Data sources", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("databases", "InfluxDB", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-influxdb`,
|
||||
"influxdb",
|
||||
[
|
||||
{ key: "api_token", value: "", encrypted: true },
|
||||
{ key: "port", value: "8086", encrypted: false },
|
||||
{ key: "host", value: "", encrypted: false },
|
||||
{ key: "protocol", value: "http", encrypted: false },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-influxdb-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-influxdb`
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-api-token"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { postgreSqlSelector } from "Selectors/postgreSql";
|
||||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonWidgetText, commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import {
|
||||
addQuery,
|
||||
|
|
@ -20,6 +20,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -50,7 +51,20 @@ describe("Data sources", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("databases", "MariaDB", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-mariadb`,
|
||||
"mariadb",
|
||||
[{ key: "connectionLimit", value: 5 }]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-mariadb-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-mariadb`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -83,7 +97,7 @@ describe("Data sources", () => {
|
|||
|
||||
cy.get(postgreSqlSelector.labelSSLCertificate).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.sslCertificate
|
||||
"SSL Certificate"
|
||||
);
|
||||
cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -3,7 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { mongoDbText } from "Texts/mongoDb";
|
||||
import { commonSelectors } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
|
||||
import {
|
||||
fillDataSourceTextField,
|
||||
|
|
@ -28,6 +28,7 @@ const data = {};
|
|||
describe("Data source MongoDB", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -56,10 +57,28 @@ describe("Data source MongoDB", () => {
|
|||
"have.text",
|
||||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
selectAndAddDataSource(
|
||||
"databases",
|
||||
mongoDbText.mongoDb,
|
||||
data.dataSourceName
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-mongodb`,
|
||||
"mongodb",
|
||||
[
|
||||
{ key: "database", value: "", encrypted: false },
|
||||
{ key: "host", value: "localhost" },
|
||||
{ key: "port", value: 27017 },
|
||||
{ key: "username", value: "" },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
{ key: "connection_type", value: "manual" },
|
||||
{ key: "connection_string", value: "", encrypted: true },
|
||||
{ key: "tls_certificate", value: "none", encrypted: false },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-mongodb-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-mongodb`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
|
|
@ -72,7 +91,7 @@ describe("Data source MongoDB", () => {
|
|||
);
|
||||
cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.labelDbName
|
||||
"Database Name"
|
||||
);
|
||||
cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -168,7 +187,7 @@ describe("Data source MongoDB", () => {
|
|||
data.dataSourceName
|
||||
);
|
||||
|
||||
cy.get('[data-cy="query-select-dropdown"]').type(
|
||||
cy.get('[data-cy="connection-type-select-dropdown"]').type(
|
||||
mongoDbText.optionConnectUsingConnectionString
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
deleteDatasource,
|
||||
verifyCouldnotConnectWithAlert,
|
||||
} from "Support/utils/dataSource";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import { realHover } from "cypress-real-events/commands/realHover";
|
||||
|
||||
const data = {};
|
||||
|
|
@ -26,6 +27,7 @@ const data = {};
|
|||
describe("Data sources MySql", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -56,7 +58,30 @@ describe("Data sources MySql", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("databases", "MySQL", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-mysql`,
|
||||
"mysql",
|
||||
[
|
||||
{ key: "connection_type", value: "hostname" },
|
||||
{ key: "host", value: "localhost" },
|
||||
{ key: "port", value: 3306 },
|
||||
{ key: "database", value: "" },
|
||||
{ key: "socket", value: "", encrypted: false },
|
||||
{ key: "username", value: "" },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
{ key: "ssl_enabled", value: false, encrypted: false },
|
||||
{ key: "ssl_certificate", value: "none", encrypted: false },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-mysql-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-mysql`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -110,7 +135,7 @@ describe("Data sources MySql", () => {
|
|||
deleteDatasource(`cypress-${data.dataSourceName}-mysql`);
|
||||
});
|
||||
|
||||
it.only("Should verify the functionality of MySQL connection form.", () => {
|
||||
it("Should verify the functionality of MySQL connection form.", () => {
|
||||
selectAndAddDataSource("databases", "MySQL", data.dataSourceName);
|
||||
|
||||
fillDataSourceTextField(
|
||||
|
|
@ -170,9 +195,9 @@ describe("Data sources MySql", () => {
|
|||
verifyCouldnotConnectWithAlert(
|
||||
"ER_ACCESS_DENIED_ERROR: Access denied for user 'root'@'103.171.99.42' (using password: YES)"
|
||||
);
|
||||
cy.get('[data-cy="-toggle-input"]').then(($el) => {
|
||||
cy.get('[data-cy="ssl-enabled-toggle-input"]').then(($el) => {
|
||||
if ($el.is(":checked")) {
|
||||
cy.get('[data-cy="-toggle-input"]').uncheck();
|
||||
cy.get('[data-cy="ssl-enabled-toggle-input"]').uncheck();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonWidgetText, commonText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
addQuery,
|
||||
fillDataSourceTextField,
|
||||
|
|
@ -20,6 +21,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -52,10 +54,31 @@ describe("Data sources", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource(
|
||||
"databases",
|
||||
postgreSqlText.postgreSQL,
|
||||
data.dataSourceName
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-postgresql`,
|
||||
"postgresql",
|
||||
[
|
||||
{ key: "connection_type", value: "manual", encrypted: false },
|
||||
{ key: "host", value: "localhost", encrypted: false },
|
||||
{ key: "port", value: 5432, encrypted: false },
|
||||
{ key: "ssl_enabled", value: true, encrypted: false },
|
||||
{ key: "ssl_certificate", value: "none", encrypted: false },
|
||||
{ key: "password", value: null, 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 },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-postgresql-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-postgresql`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonWidgetText, commonText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
addQuery,
|
||||
fillDataSourceTextField,
|
||||
|
|
@ -19,6 +20,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -49,7 +51,26 @@ describe("Data sources", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("databases", "RethinkDB", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-rethinkdb`,
|
||||
"rethinkdb",
|
||||
[
|
||||
{ key: "port", value: "28015", encrypted: false },
|
||||
{ key: "host", value: "", encrypted: false },
|
||||
{ key: "database", value: "", encrypted: false },
|
||||
{ key: "username", value: "", encrypted: false },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-rethinkdb-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-rethinkdb`
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-database"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { s3Selector } from "Selectors/awss3";
|
|||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { s3Text } from "Texts/awss3";
|
||||
import { commonSelectors } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
fillDataSourceTextField,
|
||||
selectAndAddDataSource,
|
||||
|
|
@ -51,7 +51,31 @@ describe("Data sources AWS S3", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("cloudstorage", s3Text.awsS3, data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-aws-s3`,
|
||||
"s3",
|
||||
[
|
||||
{ key: "access_key", value: "" },
|
||||
{ key: "secret_key", value: "", encrypted: true },
|
||||
{ key: "region", value: "" },
|
||||
{ key: "endpoint", value: "" },
|
||||
{ key: "endpoint_enabled", value: false, encrypted: false },
|
||||
{
|
||||
key: "instance_metadata_credentials",
|
||||
value: "iam_access_keys",
|
||||
encrypted: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-aws-s3-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-aws-s3`
|
||||
);
|
||||
cy.get(s3Selector.accessKeyLabel).verifyVisibleElement(
|
||||
"have.text",
|
||||
s3Text.accessKey
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { fake } from "Fixtures/fake";
|
|||
import { postgreSqlSelector } from "Selectors/postgreSql";
|
||||
import { postgreSqlText } from "Texts/postgreSql";
|
||||
import { commonSelectors } from "Selectors/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import {
|
||||
fillDataSourceTextField,
|
||||
selectAndAddDataSource,
|
||||
|
|
@ -14,6 +14,7 @@ const data = {};
|
|||
describe("Data source SMTP", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -43,7 +44,25 @@ describe("Data source SMTP", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("apis", "SMTP", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-smtp`,
|
||||
"smtp",
|
||||
[
|
||||
{ key: "host", value: "localhost", encrypted: false },
|
||||
{ key: "port", value: 465, encrypted: false },
|
||||
{ key: "user", value: "", encrypted: false },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-smtp-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-smtp`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { postgreSqlText } from "Texts/postgreSql";
|
|||
import { commonWidgetText, commonText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
|
||||
import {
|
||||
addQuery,
|
||||
|
|
@ -20,6 +21,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -49,8 +51,28 @@ describe("Data sources", () => {
|
|||
"have.text",
|
||||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
selectAndAddDataSource("databases", "Snowflake", data.dataSourceName);
|
||||
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-snowflake`,
|
||||
"snowflake",
|
||||
[
|
||||
{ key: "username", value: "" },
|
||||
{ key: "account", value: "" },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
{ key: "database", value: "" },
|
||||
{ key: "schema", value: "" },
|
||||
{ key: "warehouse", value: "" },
|
||||
{ key: "role", value: "" },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-snowflake-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-snowflake`
|
||||
);
|
||||
cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.labelUserName
|
||||
|
|
@ -113,7 +135,7 @@ describe("Data sources", () => {
|
|||
deleteDatasource(`cypress-${data.dataSourceName}-snowflake`);
|
||||
});
|
||||
|
||||
it.skip("Should verify the functionality of PostgreSQL connection form.", () => {
|
||||
it.skip("Should verify the functionality of snowflake connection form.", () => {
|
||||
selectAndAddDataSource("databases", "Snowflake", data.dataSourceName);
|
||||
|
||||
fillDataSourceTextField(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { postgreSqlText } from "Texts/postgreSql";
|
|||
import { commonWidgetText, commonText } from "Texts/common";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { deleteDatasource, closeDSModal } from "Support/utils/dataSource";
|
||||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
|
||||
import {
|
||||
addQuery,
|
||||
|
|
@ -21,6 +22,7 @@ const data = {};
|
|||
describe("Data sources", () => {
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.defaultWorkspaceLogin();
|
||||
data.dataSourceName = fake.lastName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -51,7 +53,28 @@ describe("Data sources", () => {
|
|||
postgreSqlText.allCloudStorage
|
||||
);
|
||||
|
||||
selectAndAddDataSource("databases", "SQL Server", data.dataSourceName);
|
||||
cy.apiCreateGDS(
|
||||
`${Cypress.env("server_host")}/api/data-sources`,
|
||||
`cypress-${data.dataSourceName}-sql-server`,
|
||||
"mssql",
|
||||
[
|
||||
{ key: "host", value: "localhost" },
|
||||
{ key: "instanceName", value: "" },
|
||||
{ key: "port", value: 1433 },
|
||||
{ key: "database", value: "" },
|
||||
{ key: "username", value: "" },
|
||||
{ key: "password", value: "", encrypted: true },
|
||||
{ key: "azure", value: false, encrypted: false },
|
||||
]
|
||||
);
|
||||
cy.reload();
|
||||
cy.get(`[data-cy="cypress-${data.dataSourceName}-sql-server-button"]`)
|
||||
.should("be.visible")
|
||||
.click();
|
||||
cy.get(dataSourceSelector.dsNameInputField).should(
|
||||
"have.value",
|
||||
`cypress-${data.dataSourceName}-sql-server`
|
||||
);
|
||||
|
||||
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -67,7 +90,7 @@ describe("Data sources", () => {
|
|||
);
|
||||
cy.get(postgreSqlSelector.labelDbName).verifyVisibleElement(
|
||||
"have.text",
|
||||
postgreSqlText.labelDbName
|
||||
"Database Name"
|
||||
);
|
||||
cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -78,8 +101,8 @@ describe("Data sources", () => {
|
|||
"Password"
|
||||
);
|
||||
|
||||
cy.get('[data-cy="label-azure"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
cy.get('[data-cy^="label-azure-"]').verifyVisibleElement(
|
||||
"contain",
|
||||
"Azure"
|
||||
);
|
||||
cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement(
|
||||
|
|
@ -135,7 +158,7 @@ describe("Data sources", () => {
|
|||
"1433"
|
||||
);
|
||||
fillDataSourceTextField(
|
||||
postgreSqlText.labelDbName,
|
||||
"Database Name",
|
||||
postgreSqlText.placeholderNameOfDB,
|
||||
Cypress.env("sqlserver_db")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export const verifyControlComponentAction = (widgetName, value) => {
|
|||
|
||||
export const addBasicData = (data) => {
|
||||
openEditorSidebar(buttonText.defaultWidgetName);
|
||||
verifyAndModifyParameter(buttonText.buttonTextLabel, data.widgetName);
|
||||
verifyAndModifyParameter('Label', data.widgetName);
|
||||
|
||||
openAccordion(commonWidgetText.accordionEvents);
|
||||
addDefaultEventHandler(data.alertMessage);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { commonText } from "Texts/common";
|
|||
import { dataSourceSelector } from "Selectors/dataSource";
|
||||
import { dataSourceText } from "Texts/dataSource";
|
||||
import { navigateToAppEditor } from "Support/utils/common";
|
||||
import { verifyAppDelete } from "Support/utils/dashboard";
|
||||
|
||||
export const verifyCouldnotConnectWithAlert = (dangerText) => {
|
||||
cy.get(postgreSqlSelector.connectionFailedText, {
|
||||
|
|
@ -60,6 +61,15 @@ export const deleteDatasource = (datasourceName) => {
|
|||
// " Databases"
|
||||
// );
|
||||
};
|
||||
export const deleteAppandDatasourceAfterExecution = (
|
||||
appName,
|
||||
datasourceName
|
||||
) => {
|
||||
cy.backToApps();
|
||||
cy.deleteApp(appName);
|
||||
verifyAppDelete(appName);
|
||||
deleteDatasource(datasourceName);
|
||||
};
|
||||
|
||||
export const closeDSModal = () => {
|
||||
cy.get("body").then(($body) => {
|
||||
|
|
@ -96,9 +106,7 @@ export const addQueryN = (queryName, query, dbName) => {
|
|||
export const addQuery = (queryName, query, dbName) => {
|
||||
cy.get('[data-cy="show-ds-popover-button"]').click();
|
||||
cy.get(".css-4e90k9").type(`${dbName}`);
|
||||
cy.intercept("POST", "/api/data-queries/**").as(
|
||||
"createQuery"
|
||||
);
|
||||
cy.intercept("POST", "/api/data-queries/**").as("createQuery");
|
||||
cy.contains(`[id*="react-select-"]`, dbName).click();
|
||||
|
||||
cy.get('[data-cy="query-rename-input"]').clear().type(queryName);
|
||||
|
|
@ -225,7 +233,14 @@ export const createDataQuery = (appName, url, key, value) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const createRestAPIQuery = (queryName, dsName, key = '', value = '', url = "", run = true) => {
|
||||
export const createRestAPIQuery = (
|
||||
queryName,
|
||||
dsName,
|
||||
key = "",
|
||||
value = "",
|
||||
url = "",
|
||||
run = true
|
||||
) => {
|
||||
cy.getCookie("tj_auth_token").then((cookie) => {
|
||||
const headers = {
|
||||
"Tj-Workspace-Id": Cypress.env("workspaceId"),
|
||||
|
|
|
|||
27
docker/ce-entrypoint.sh
Executable file
27
docker/ce-entrypoint.sh
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ -d "./server/dist" ]; then
|
||||
SETUP_CMD='npm run db:setup:prod'
|
||||
else
|
||||
SETUP_CMD='npm run db:setup'
|
||||
fi
|
||||
|
||||
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 "$@"
|
||||
|
|
@ -88,12 +88,13 @@ 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/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/ce-entrypoint.sh ./app/server/entrypoint.sh
|
||||
|
||||
# Define non-sudo user
|
||||
RUN useradd --create-home --home-dir /home/appuser appuser \
|
||||
&& chown -R appuser:0 /app \
|
||||
|
|
@ -111,5 +112,4 @@ WORKDIR /app
|
|||
# Dependencies for scripts outside nestjs
|
||||
RUN npm install dotenv@10.0.0 joi@17.4.1
|
||||
|
||||
|
||||
ENTRYPOINT ["./server/entrypoint.sh"]
|
||||
|
|
|
|||
|
|
@ -145,12 +145,13 @@ COPY --from=builder /app/frontend/build ./app/frontend/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/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/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
|
||||
|
||||
# Define non-sudo user
|
||||
RUN useradd --create-home --home-dir /home/appuser appuser \
|
||||
&& chown -R appuser:0 /app \
|
||||
|
|
@ -214,4 +215,4 @@ RUN npm install dotenv@10.0.0 joi@17.4.1
|
|||
|
||||
RUN npm cache clean --force
|
||||
|
||||
ENTRYPOINT ["./server/entrypoint.sh"]
|
||||
ENTRYPOINT ["./server/ee-entrypoint.sh"]
|
||||
|
|
|
|||
15
docker/ee/ee-try-entrypoint-lts.sh
Executable file
15
docker/ee/ee-try-entrypoint-lts.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Start Redis
|
||||
# service redis-server start
|
||||
# redis-server /etc/redis/redis.conf
|
||||
|
||||
# Start Postgres
|
||||
service postgresql start
|
||||
|
||||
# Export the PORT variable to be used by the application
|
||||
export PORT=${PORT:-80}
|
||||
|
||||
# Start Supervisor
|
||||
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||
2
server/try-entrypoint.sh → docker/ee/ee-try-entrypoint.sh
Normal file → Executable file
2
server/try-entrypoint.sh → docker/ee/ee-try-entrypoint.sh
Normal file → Executable file
|
|
@ -22,7 +22,7 @@ echo "Starting Temporal Server..."
|
|||
export PORT=${PORT:-80}
|
||||
|
||||
# Start Supervisor
|
||||
/usr/bin/supervisord -n &
|
||||
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf &
|
||||
|
||||
# Wait for Temporal Server to be ready
|
||||
echo "Waiting for Temporal Server to be ready..."
|
||||
|
|
@ -1,21 +1,31 @@
|
|||
FROM tooljet/tooljet-ce:latest
|
||||
FROM tooljet/tooljet:ee-lts-latest
|
||||
|
||||
# copy postgrest executable
|
||||
COPY --from=postgrest/postgrest:v10.1.1.20221215 /bin/postgrest /bin
|
||||
# Copy PostgREST executable
|
||||
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
|
||||
|
||||
# Install Postgres
|
||||
# Install PostgreSQL
|
||||
USER root
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
RUN echo "deb http://deb.debian.org/debian"
|
||||
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
|
||||
|
||||
USER postgres
|
||||
RUN service postgresql start && \
|
||||
psql -c "create role tooljet with login superuser password 'postgres';"
|
||||
USER root
|
||||
|
||||
# Install Redis
|
||||
RUN apt update && apt -y install redis
|
||||
|
||||
# Create appuser home & ensure permission for supervisord and services
|
||||
RUN mkdir -p /var/log/supervisor /var/run/postgresql /var/lib/postgresql /var/lib/redis && \
|
||||
chown -R appuser:appuser /etc/supervisor /var/log/supervisor /var/lib/redis && \
|
||||
chown -R postgres:postgres /var/run/postgresql /var/lib/postgresql
|
||||
|
||||
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
|
||||
RUN echo "[supervisord] \n" \
|
||||
"nodaemon=true \n" \
|
||||
"user=root \n" \
|
||||
"\n" \
|
||||
"[program:postgrest] \n" \
|
||||
"command=/bin/postgrest \n" \
|
||||
|
|
@ -23,12 +33,23 @@ RUN echo "[supervisord] \n" \
|
|||
"autorestart=true \n" \
|
||||
"\n" \
|
||||
"[program:tooljet] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/bin/bash -c '/app/server/scripts/init-db-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" \
|
||||
"\n" \
|
||||
"[program:redis] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/usr/bin/redis-server \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
|
||||
|
|
@ -49,10 +70,17 @@ ENV TOOLJET_HOST=http://localhost \
|
|||
PGRST_HOST=http://localhost:3000 \
|
||||
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
|
||||
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
|
||||
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
|
||||
ORM_LOGGING=true \
|
||||
DEPLOYMENT_PLATFORM=docker:local \
|
||||
HOME=/home/appuser \
|
||||
REDIS_HOST=localhost \
|
||||
REDIS_PORT=6379 \
|
||||
REDIS_USER=default \
|
||||
REDIS_PASS= \
|
||||
TERM=xterm
|
||||
|
||||
# Prepare DB and start application
|
||||
ENTRYPOINT service postgresql start 1> /dev/null && /usr/bin/supervisord
|
||||
# Set the entrypoint
|
||||
COPY ./docker/ee/ee-try-entrypoint-lts.sh /ee-try-entrypoint-lts.sh
|
||||
RUN chmod +x /ee-try-entrypoint-lts
|
||||
ENTRYPOINT ["/ee-try-entrypoint-lts.sh"]
|
||||
117
docker/ee/ee-try-tooljet.Dockerfile
Normal file
117
docker/ee/ee-try-tooljet.Dockerfile
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
FROM tooljet/tooljet:ee-latest
|
||||
|
||||
# Copy postgrest executable
|
||||
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
|
||||
|
||||
# Install Postgres
|
||||
USER root
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
RUN echo "deb http://deb.debian.org/debian"
|
||||
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
|
||||
USER postgres
|
||||
RUN service postgresql start && \
|
||||
psql -c "create role tooljet with login superuser password 'postgres';"
|
||||
USER root
|
||||
|
||||
|
||||
RUN apt update && apt -y install redis
|
||||
|
||||
# Create appuser home & ensure permission for supervisord and services
|
||||
RUN mkdir -p /var/log/supervisor /var/run/postgresql /var/lib/postgresql /var/lib/redis && \
|
||||
chown -R appuser:appuser /etc/supervisor /var/log/supervisor /var/lib/redis && \
|
||||
chown -R postgres:postgres /var/run/postgresql /var/lib/postgresql
|
||||
|
||||
# Install Temporal Server Binaries
|
||||
RUN curl -OL https://github.com/temporalio/temporal/releases/download/v1.24.2/temporal_1.24.2_linux_amd64.tar.gz && \
|
||||
tar -xzf temporal_1.24.2_linux_amd64.tar.gz && \
|
||||
mv temporal-server /usr/bin/temporal-server && \
|
||||
chmod +x /usr/bin/temporal-server && \
|
||||
rm temporal_1.24.2_linux_amd64.tar.gz
|
||||
|
||||
# Install Temporal UI Server Binaries
|
||||
RUN curl -OL https://github.com/temporalio/ui-server/releases/download/v2.28.0/ui-server_2.28.0_linux_amd64.tar.gz && \
|
||||
tar -xzf ui-server_2.28.0_linux_amd64.tar.gz && \
|
||||
mv ui-server /usr/bin/temporal-ui-server && \
|
||||
chmod +x /usr/bin/temporal-ui-server && \
|
||||
rm ui-server_2.28.0_linux_amd64.tar.gz
|
||||
|
||||
# Copy Temporal configuration files
|
||||
COPY ./docker/ee/temporal-server.yaml /etc/temporal/temporal-server.yaml
|
||||
COPY ./docker/ee/temporal-ui-server.yaml /etc/temporal/temporal-ui-server.yaml
|
||||
|
||||
# Install grpcurl
|
||||
RUN apt update && apt install -y curl \
|
||||
&& curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.0/grpcurl_1.8.0_linux_x86_64.tar.gz | tar -xzv -C /usr/local/bin grpcurl
|
||||
|
||||
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
|
||||
RUN echo "[supervisord] \n" \
|
||||
"nodaemon=true \n" \
|
||||
"user=root \n" \
|
||||
"\n" \
|
||||
"[program:postgrest] \n" \
|
||||
"command=/bin/postgrest \n" \
|
||||
"autostart=true \n" \
|
||||
"autorestart=true \n" \
|
||||
"\n" \
|
||||
"[program:tooljet] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/bin/bash -c '/app/server/scripts/init-db-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" \
|
||||
"\n" \
|
||||
"[program:redis] \n" \
|
||||
"user=appuser \n" \
|
||||
"command=/usr/bin/redis-server \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 \
|
||||
TOOLJET_SERVER_URL=http://localhost \
|
||||
PORT=80 \
|
||||
NODE_ENV=production \
|
||||
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
|
||||
SECRET_KEY_BASE=replace_with_secret_key_base \
|
||||
PG_DB=tooljet_production \
|
||||
PG_USER=tooljet \
|
||||
PG_PASS=postgres \
|
||||
PG_HOST=localhost \
|
||||
ENABLE_TOOLJET_DB=true \
|
||||
TOOLJET_DB_HOST=localhost \
|
||||
TOOLJET_DB_USER=tooljet \
|
||||
TOOLJET_DB_PASS=postgres \
|
||||
TOOLJET_DB=tooljet_db \
|
||||
PGRST_HOST=http://localhost:3000 \
|
||||
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
|
||||
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
|
||||
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
|
||||
ORM_LOGGING=true \
|
||||
DEPLOYMENT_PLATFORM=docker:local \
|
||||
HOME=/home/appuser \
|
||||
REDIS_HOST=localhost \
|
||||
REDIS_PORT=6379 \
|
||||
REDIS_USER=default \
|
||||
REDIS_PASS= \
|
||||
ENABLE_MARKETPLACE_FEATURE=true \
|
||||
TERM=xterm \
|
||||
ENABLE_WORKFLOW_SCHEDULING=true \
|
||||
TEMPORAL_SERVER_ADDRESS=localhost:7233 \
|
||||
TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS=tooljet-workflows \
|
||||
TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE=default \
|
||||
TEMPORAL_ADDRESS=localhost:7233 \
|
||||
TEMPORAL_CORS_ORIGINS=http://localhost:8080
|
||||
|
||||
# Set the entrypoint
|
||||
COPY ./docker/ee/ee-try-entrypoint.sh /ee-try-entrypoint.sh
|
||||
RUN chmod +x /ee-try-entrypoint.sh
|
||||
ENTRYPOINT ["/ee-try-entrypoint.sh"]
|
||||
75
docker/ee/temporal-server.yaml
Normal file
75
docker/ee/temporal-server.yaml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
log:
|
||||
stdout: true
|
||||
level: info
|
||||
|
||||
persistence:
|
||||
defaultStore: sqlite-default
|
||||
visibilityStore: sqlite-visibility
|
||||
numHistoryShards: 4
|
||||
datastores:
|
||||
sqlite-default:
|
||||
sql:
|
||||
pluginName: "sqlite"
|
||||
databaseName: "/etc/temporal/default.db"
|
||||
connectAddr: "localhost"
|
||||
connectProtocol: "tcp"
|
||||
connectAttributes:
|
||||
cache: "private"
|
||||
setup: true
|
||||
|
||||
sqlite-visibility:
|
||||
sql:
|
||||
pluginName: "sqlite"
|
||||
databaseName: "/etc/temporal/visibility.db"
|
||||
connectAddr: "localhost"
|
||||
connectProtocol: "tcp"
|
||||
connectAttributes:
|
||||
cache: "private"
|
||||
setup: true
|
||||
|
||||
global:
|
||||
membership:
|
||||
maxJoinDuration: 30s
|
||||
broadcastAddress: "127.0.0.1"
|
||||
pprof:
|
||||
port: 7936
|
||||
|
||||
services:
|
||||
frontend:
|
||||
rpc:
|
||||
grpcPort: 7233
|
||||
membershipPort: 6933
|
||||
bindOnLocalHost: true
|
||||
httpPort: 7243
|
||||
|
||||
matching:
|
||||
rpc:
|
||||
grpcPort: 7235
|
||||
membershipPort: 6935
|
||||
bindOnLocalHost: true
|
||||
|
||||
history:
|
||||
rpc:
|
||||
grpcPort: 7234
|
||||
membershipPort: 6934
|
||||
bindOnLocalHost: true
|
||||
|
||||
worker:
|
||||
rpc:
|
||||
membershipPort: 6939
|
||||
|
||||
clusterMetadata:
|
||||
enableGlobalNamespace: false
|
||||
failoverVersionIncrement: 10
|
||||
masterClusterName: "active"
|
||||
currentClusterName: "active"
|
||||
clusterInformation:
|
||||
active:
|
||||
enabled: true
|
||||
initialFailoverVersion: 1
|
||||
rpcName: "frontend"
|
||||
rpcAddress: "localhost:7236"
|
||||
httpAddress: "localhost:7243"
|
||||
|
||||
dcRedirectionPolicy:
|
||||
policy: "noop"
|
||||
8
docker/ee/temporal-ui-server.yaml
Normal file
8
docker/ee/temporal-ui-server.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
temporalGrpcAddress: 127.0.0.1:7233 # Use the correct Temporal server address
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
enableUi: true
|
||||
cors:
|
||||
allowOrigins:
|
||||
- http://localhost:8080
|
||||
defaultNamespace: default
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit dcd948d284b5f14a868480830e09b90496db8572
|
||||
Subproject commit a1435b3b0e66a0c731812256d3d495d5bf48d5bb
|
||||
|
|
@ -579,6 +579,11 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
keepRatio={false}
|
||||
individualGroupableProps={individualGroupableProps}
|
||||
onResize={(e) => {
|
||||
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) {
|
||||
|
|
@ -639,9 +644,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
return false;
|
||||
}
|
||||
handleActivateNonDraggingComponents();
|
||||
useGridStore.getState().actions.setResizingComponentId(e.target.id);
|
||||
e.setMin([gridWidth, GRID_HEIGHT]);
|
||||
showGridLines();
|
||||
}}
|
||||
onResizeEnd={(e) => {
|
||||
try {
|
||||
|
|
@ -945,7 +948,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
const isParentModal = isParentNewModal || isParentLegacyModal || isParentModalSlot;
|
||||
|
||||
if (isParentModal) {
|
||||
const modalContainer = e.target.closest('.tj-modal-widget-content');
|
||||
const modalContainer = e.target.closest('.tj-modal--container');
|
||||
const mainCanvas = document.getElementById('real-canvas');
|
||||
|
||||
const mainRect = mainCanvas.getBoundingClientRect();
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ const RenderWidget = ({
|
|||
onComponentClick={onComponentClick}
|
||||
darkMode={darkMode}
|
||||
componentName={componentName}
|
||||
dataCy={`draggable-widget-${componentName}`}
|
||||
/>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { keymap } from '@codemirror/view';
|
|||
import { completionKeymap, acceptCompletion, autocompletion, completionStatus } from '@codemirror/autocomplete';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import _ from 'lodash';
|
||||
import { sass, sassCompletionSource } from '@codemirror/lang-sass';
|
||||
import { okaidia } from '@uiw/codemirror-theme-okaidia';
|
||||
import { githubLight } from '@uiw/codemirror-theme-github';
|
||||
|
|
@ -21,6 +22,8 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { shallow } from 'zustand/shallow';
|
||||
import { search, searchKeymap, searchPanelOpen } from '@codemirror/search';
|
||||
import { handleSearchPanel, SearchBtn } from './SearchBox';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
import { isInsideParent } from './utils';
|
||||
|
||||
const langSupport = Object.freeze({
|
||||
javascript: javascript(),
|
||||
|
|
@ -51,8 +54,15 @@ const MultiLineCodeEditor = (props) => {
|
|||
renderCopilot,
|
||||
} = props;
|
||||
const replaceIdsWithName = useStore((state) => state.replaceIdsWithName, shallow);
|
||||
const wrapperRef = useRef(null);
|
||||
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
|
||||
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
|
||||
|
||||
const isInsideQueryPane = !!document.querySelector('.code-hinter-wrapper')?.closest('.query-details');
|
||||
const isInsideQueryManager = useMemo(
|
||||
() => isInsideParent(wrapperRef?.current, 'query-manager'),
|
||||
[wrapperRef.current]
|
||||
);
|
||||
|
||||
const context = useContext(CodeHinterContext);
|
||||
|
||||
|
|
@ -64,6 +74,8 @@ const MultiLineCodeEditor = (props) => {
|
|||
|
||||
const [editorView, setEditorView] = React.useState(null);
|
||||
|
||||
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline');
|
||||
|
||||
const handleOnBlur = () => {
|
||||
if (!delayOnChange) return onChange(currentValueRef.current);
|
||||
setTimeout(() => {
|
||||
|
|
@ -85,6 +97,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
highlightActiveLine: false,
|
||||
autocompletion: hideSuggestion ?? true,
|
||||
highlightActiveLineGutter: false,
|
||||
defaultKeymap: false,
|
||||
completionKeymap: true,
|
||||
searchKeymap: false,
|
||||
};
|
||||
|
|
@ -100,9 +113,16 @@ const MultiLineCodeEditor = (props) => {
|
|||
|
||||
const hints = getSuggestions();
|
||||
|
||||
const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
|
||||
|
||||
const allHints = {
|
||||
...hints,
|
||||
appHints: [...hints.appHints, ...serverHints],
|
||||
};
|
||||
|
||||
let JSLangHints = [];
|
||||
if (lang === 'javascript') {
|
||||
JSLangHints = Object.keys(hints['jsHints'])
|
||||
JSLangHints = Object.keys(allHints['jsHints'])
|
||||
.map((key) => {
|
||||
return hints['jsHints'][key]['methods'].map((hint) => ({
|
||||
hint: hint,
|
||||
|
|
@ -120,7 +140,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
});
|
||||
}
|
||||
|
||||
const appHints = hints['appHints'];
|
||||
const appHints = allHints['appHints'];
|
||||
|
||||
let autoSuggestionList = appHints.filter((suggestion) => {
|
||||
return suggestion.hint.includes(nearestSubstring);
|
||||
|
|
@ -187,7 +207,12 @@ const MultiLineCodeEditor = (props) => {
|
|||
};
|
||||
}
|
||||
|
||||
const customKeyMaps = [...defaultKeymap, ...completionKeymap, ...searchKeymap];
|
||||
const customKeyMaps = [
|
||||
...defaultKeymap.filter((keyBinding) => keyBinding.key !== 'Mod-Enter'), // Remove default keybinding for Mod-Enter
|
||||
...completionKeymap,
|
||||
...searchKeymap,
|
||||
];
|
||||
|
||||
const customTabKeymap = keymap.of([
|
||||
{
|
||||
key: 'Tab',
|
||||
|
|
@ -208,6 +233,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
return true;
|
||||
},
|
||||
},
|
||||
...queryPanelKeybindings,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -229,6 +255,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
<div
|
||||
className={`code-hinter-wrapper position-relative ${isInsideQueryPane ? 'code-editor-query-panel' : ''}`}
|
||||
style={{ width: '100%' }}
|
||||
ref={wrapperRef}
|
||||
>
|
||||
<div className={`${className} ${darkMode && 'cm-codehinter-dark-themed'}`}>
|
||||
<SearchBtn view={editorView} />
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ export const PreviewBox = ({
|
|||
const [largeDataset, setLargeDataset] = useState(false);
|
||||
const globals = useStore((state) => state.getAllExposedValues().constants || {}, shallow);
|
||||
const secrets = useStore((state) => state.getSecrets(), shallow);
|
||||
const globalServerConstantsRegex = /^\{\{.*globals\.server.*\}\}$/;
|
||||
|
||||
const getPreviewContent = (content, type) => {
|
||||
if (content === undefined || content === null) return currentValue;
|
||||
|
|
@ -118,11 +119,11 @@ export const PreviewBox = ({
|
|||
let previewContent = resolvedValue;
|
||||
let isGlobalConstant = currentValue && currentValue.includes('{{constants.');
|
||||
let isSecretConstant = currentValue && currentValue.includes('{{secrets.');
|
||||
const isServerConstant = currentValue && currentValue.match(globalServerConstantsRegex);
|
||||
let invalidConstants = null;
|
||||
let undefinedError = null;
|
||||
if (isGlobalConstant || isSecretConstant) {
|
||||
invalidConstants = verifyConstant(currentValue, globals, secrets);
|
||||
console.log('invalidConstants', invalidConstants);
|
||||
}
|
||||
if (invalidConstants?.length) {
|
||||
undefinedError = { type: 'Invalid constants' };
|
||||
|
|
@ -197,7 +198,11 @@ export const PreviewBox = ({
|
|||
const errValue = ifCoersionErrorHasCircularDependency(_resolveValue);
|
||||
|
||||
setError({
|
||||
message: isSecretError ? 'secrets cannot be used in apps' : _error,
|
||||
message: isServerConstant
|
||||
? 'Server variables cannot be used in apps'
|
||||
: isSecretError
|
||||
? 'secrets cannot be used in apps'
|
||||
: _error,
|
||||
value: isSecretError
|
||||
? 'Undefined'
|
||||
: jsErrorType === 'Invalid'
|
||||
|
|
@ -222,6 +227,7 @@ export const PreviewBox = ({
|
|||
isWorkspaceVariable={isWorkspaceVariable}
|
||||
isSecretConstant={isSecretConstant || false}
|
||||
isLargeDataset={largeDataset}
|
||||
isServerConstant={isServerConstant}
|
||||
/>
|
||||
<CodeHinter.PopupIcon
|
||||
callback={() => copyToClipboard(error ? error?.value : content)}
|
||||
|
|
@ -240,8 +246,11 @@ const RenderResolvedValue = ({
|
|||
withValidation,
|
||||
isWorkspaceVariable,
|
||||
isSecretConstant = false,
|
||||
isServerConstant = false,
|
||||
isLargeDataset,
|
||||
}) => {
|
||||
const isServerSideGlobalEnabled = useStore((state) => !!state?.license?.featureAccess?.serverSideGlobal, shallow);
|
||||
|
||||
const computeCoersionPreview = (resolvedValue, coersionData) => {
|
||||
if (coersionData?.typeBeforeCoercion === coersionData?.typeAfterCoercion) return resolvedValue;
|
||||
|
||||
|
|
@ -264,7 +273,11 @@ const RenderResolvedValue = ({
|
|||
}`
|
||||
: previewType;
|
||||
|
||||
const previewContent = isSecretConstant
|
||||
const previewContent = isServerConstant
|
||||
? isServerSideGlobalEnabled
|
||||
? 'Server variables would be resolved at runtime'
|
||||
: 'Server variables are only available in paid plans'
|
||||
: isSecretConstant
|
||||
? 'Values of secret constants are hidden'
|
||||
: !withValidation
|
||||
? resolvedValue
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable import/no-unresolved */
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { PreviewBox } from './PreviewBox';
|
||||
import { ToolTip } from '@/Editor/Inspector/Elements/Components/ToolTip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { camelCase, isEmpty, noop } from 'lodash';
|
||||
import { camelCase, isEmpty, noop, get } from 'lodash';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { autocompletion, completionKeymap, completionStatus, acceptCompletion } from '@codemirror/autocomplete';
|
||||
|
|
@ -12,7 +12,7 @@ import { keymap } from '@codemirror/view';
|
|||
import FxButton from '../CodeBuilder/Elements/FxButton';
|
||||
import cx from 'classnames';
|
||||
import { DynamicFxTypeRenderer } from './DynamicFxTypeRenderer';
|
||||
import { resolveReferences } from './utils';
|
||||
import { isInsideParent, resolveReferences } from './utils';
|
||||
import { okaidia } from '@uiw/codemirror-theme-okaidia';
|
||||
import { githubLight } from '@uiw/codemirror-theme-github';
|
||||
import { getAutocompletion } from './autocompleteExtensionConfig';
|
||||
|
|
@ -22,6 +22,7 @@ import CodeHinter from './CodeHinter';
|
|||
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
|
||||
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
|
||||
const { initialValue, onChange, enablePreview = true, portalProps } = restProps;
|
||||
|
|
@ -161,6 +162,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
|
|||
componentName={componentName}
|
||||
setShowPreview={setShowPreview}
|
||||
showPreview={showPreview}
|
||||
wrapperRef={wrapperRef}
|
||||
showSuggestions={showSuggestions}
|
||||
{...restProps}
|
||||
/>
|
||||
|
|
@ -194,11 +196,27 @@ const EditorInput = ({
|
|||
previewRef,
|
||||
setShowPreview,
|
||||
onInputChange,
|
||||
wrapperRef,
|
||||
showSuggestions,
|
||||
}) => {
|
||||
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
|
||||
|
||||
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
|
||||
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline');
|
||||
|
||||
const isInsideQueryManager = useMemo(
|
||||
() => isInsideParent(wrapperRef?.current, 'query-manager'),
|
||||
[wrapperRef.current]
|
||||
);
|
||||
function autoCompleteExtensionConfig(context) {
|
||||
const hints = getSuggestions();
|
||||
const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
|
||||
|
||||
const allHints = {
|
||||
...hints,
|
||||
appHints: [...hints.appHints, ...serverHints],
|
||||
};
|
||||
|
||||
let word = context.matchBefore(/\w*/);
|
||||
|
||||
const totalReferences = (context.state.doc.toString().match(/{{/g) || []).length;
|
||||
|
|
@ -229,7 +247,7 @@ const EditorInput = ({
|
|||
queryInput = '{{' + currentWord + '}}';
|
||||
}
|
||||
|
||||
let completions = getAutocompletion(queryInput, validationType, hints, totalReferences, originalQueryInput);
|
||||
let completions = getAutocompletion(queryInput, validationType, allHints, totalReferences, originalQueryInput);
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
|
|
@ -239,7 +257,7 @@ const EditorInput = ({
|
|||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), []);
|
||||
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager]);
|
||||
|
||||
const autoCompleteConfig = autocompletion({
|
||||
override: [overRideFunction],
|
||||
|
|
@ -256,7 +274,10 @@ const EditorInput = ({
|
|||
maxRenderedOptions: 10,
|
||||
});
|
||||
|
||||
const customKeyMaps = [...defaultKeymap, ...completionKeymap];
|
||||
const customKeyMaps = [
|
||||
...defaultKeymap.filter((keyBinding) => keyBinding.key !== 'Mod-Enter'), // Remove default keybinding for Mod-Enter
|
||||
...completionKeymap,
|
||||
];
|
||||
const customTabKeymap = keymap.of([
|
||||
{
|
||||
key: 'Tab',
|
||||
|
|
@ -278,6 +299,7 @@ const EditorInput = ({
|
|||
}
|
||||
},
|
||||
},
|
||||
...queryPanelKeybindings,
|
||||
]);
|
||||
|
||||
const handleOnChange = React.useCallback((val) => {
|
||||
|
|
@ -409,11 +431,11 @@ const EditorInput = ({
|
|||
extensions={
|
||||
showSuggestions
|
||||
? [
|
||||
javascript({ jsx: lang === 'jsx' }),
|
||||
autoCompleteConfig,
|
||||
keymap.of([...customKeyMaps]),
|
||||
customTabKeymap,
|
||||
]
|
||||
javascript({ jsx: lang === 'jsx' }),
|
||||
autoCompleteConfig,
|
||||
keymap.of([...customKeyMaps]),
|
||||
customTabKeymap,
|
||||
]
|
||||
: [javascript({ jsx: lang === 'jsx' })]
|
||||
}
|
||||
onChange={(val) => {
|
||||
|
|
@ -427,7 +449,8 @@ const EditorInput = ({
|
|||
bracketMatching: true,
|
||||
foldGutter: false,
|
||||
highlightActiveLine: false,
|
||||
autocompletion: showSuggestions,
|
||||
autocompletion: true,
|
||||
defaultKeymap: false,
|
||||
completionKeymap: true,
|
||||
searchKeymap: false,
|
||||
}}
|
||||
|
|
@ -485,9 +508,8 @@ const DynamicEditorBridge = (props) => {
|
|||
<ToolTip
|
||||
label={t(`widget.commonProperties.${camelCase(paramLabel)}`, paramLabel)}
|
||||
meta={fieldMeta}
|
||||
labelClass={`tj-text-xsm color-slate12 ${codeShow ? 'mb-2' : 'mb-0'} ${
|
||||
darkMode && 'color-whitish-darkmode'
|
||||
}`}
|
||||
labelClass={`tj-text-xsm color-slate12 ${codeShow ? 'mb-2' : 'mb-0'} ${darkMode && 'color-whitish-darkmode'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -495,9 +517,8 @@ const DynamicEditorBridge = (props) => {
|
|||
<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'
|
||||
}`}
|
||||
className={`col-auto pt-0 fx-common fx-button-container ${(isEventManagerParam || codeShow) && 'show-fx-button-container'
|
||||
}`}
|
||||
>
|
||||
<FxButton
|
||||
active={codeShow}
|
||||
|
|
|
|||
|
|
@ -220,6 +220,9 @@
|
|||
.query-hinter{
|
||||
flex-grow: 1;
|
||||
}
|
||||
.cm-editor {
|
||||
min-height: 150px !important;
|
||||
}
|
||||
}
|
||||
.code-editor-query-panel{
|
||||
&.show-line-numbers{
|
||||
|
|
@ -398,6 +401,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.rest-api-body-codehinter {
|
||||
.cm-editor {
|
||||
min-height: 150px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.border-danger {
|
||||
.cm-editor {
|
||||
border: 1px solid red !important;
|
||||
|
|
|
|||
58
frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js
Normal file
58
frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const useQueryPanelKeyHooks = (onChange, value, type) => {
|
||||
const queryPanelHeight = useStore((state) => state.queryPanel.queryPanelHeight);
|
||||
const runQueryOnShortcut = useStore((state) => state.queryPanel.runQueryOnShortcut);
|
||||
const previewQueryOnShortcut = useStore((state) => state.queryPanel.previewQueryOnShortcut);
|
||||
const moduleId = useModuleId();
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
|
||||
const [queryPanelKeybindings, setQueryPanelKeybindings] = useState([]);
|
||||
|
||||
const handleRunQuery = useCallback(
|
||||
(view) => {
|
||||
const isEditor = pathname.includes('/apps/');
|
||||
if (queryPanelHeight !== 0 && isEditor) {
|
||||
onChange(type === 'multiline' ? value.current : value);
|
||||
runQueryOnShortcut();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[queryPanelHeight, onChange, runQueryOnShortcut, value]
|
||||
);
|
||||
|
||||
const handlePreviewQuery = useCallback(
|
||||
(view) => {
|
||||
const isEditor = pathname.includes('/apps/');
|
||||
if (queryPanelHeight !== 0 && isEditor) {
|
||||
onChange(type === 'multiline' ? value.current : value);
|
||||
previewQueryOnShortcut(moduleId);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[queryPanelHeight, moduleId, onChange, previewQueryOnShortcut, value]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setQueryPanelKeybindings([
|
||||
{
|
||||
key: 'Mod-Enter',
|
||||
preventDefault: true,
|
||||
run: handleRunQuery,
|
||||
},
|
||||
{
|
||||
key: 'Mod-Shift-Enter',
|
||||
preventDefault: true,
|
||||
run: handlePreviewQuery,
|
||||
},
|
||||
]);
|
||||
}, [handleRunQuery, handlePreviewQuery]);
|
||||
|
||||
return {
|
||||
queryPanelKeybindings,
|
||||
};
|
||||
};
|
||||
|
|
@ -30,6 +30,17 @@ function traverseAST(node, callback) {
|
|||
}
|
||||
}
|
||||
|
||||
export const isInsideParent = (element, className) => {
|
||||
while (element) {
|
||||
if (element.classList?.contains(className)) {
|
||||
console.log('element.classList', element.classList);
|
||||
return true;
|
||||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function getMethods(type) {
|
||||
const arrayMethods = Object.getOwnPropertyNames(Array.prototype).filter(
|
||||
(p) => typeof Array.prototype[p] === 'function'
|
||||
|
|
|
|||
|
|
@ -381,7 +381,6 @@ export const BaseQueryManagerBody = ({ darkMode, activeTab, renderCopilot = () =
|
|||
{activeTab === 1 && renderQueryElement()}
|
||||
{activeTab === 2 && renderTransformation()}
|
||||
{activeTab === 3 && renderQueryOptions()}
|
||||
<div className="pb-5" />
|
||||
<Preview darkMode={darkMode} calculatePreviewHeight={calculatePreviewHeight} />
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import React, { useState, forwardRef, useRef, useEffect, useCallback } from 'react';
|
||||
import RenameIcon from '../Icons/RenameIcon';
|
||||
import Eye1 from '@/_ui/Icon/solidIcons/Eye1';
|
||||
import Play from '@/_ui/Icon/solidIcons/Play';
|
||||
import cx from 'classnames';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DATA_SOURCE_TYPE } from '@/_helpers/constants';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { ToolTip } from '@/_components';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { decodeEntities } from '@/_helpers/utils';
|
||||
import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => {
|
||||
|
|
@ -27,6 +26,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTa
|
|||
const setShowCreateQuery = useStore((state) => state.queryPanel.setShowCreateQuery);
|
||||
const queryName = selectedQuery?.name ?? '';
|
||||
const shouldFreeze = useStore((state) => state.getShouldFreeze());
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedQuery?.name) {
|
||||
setShowCreateQuery(false);
|
||||
|
|
@ -244,34 +244,26 @@ const RunButton = ({ buttonLoadingState }) => {
|
|||
const isLoading = useStore(
|
||||
(state) => state.resolvedStore.modules.canvas.exposedValues.queries[selectedQuery?.id]?.isLoading ?? false
|
||||
);
|
||||
const isMac = typeof navigator !== 'undefined' && navigator?.userAgent?.toLowerCase().includes('mac');
|
||||
|
||||
const shortcutDisplay = isMac ? 'Run query ⌘↩' : 'Run query Ctrl+Enter';
|
||||
return (
|
||||
<span
|
||||
{...(isInDraft && {
|
||||
'data-tooltip-id': 'query-header-btn-run',
|
||||
'data-tooltip-content': 'Connect a data source to run',
|
||||
})}
|
||||
>
|
||||
<button
|
||||
onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)}
|
||||
className={`border-0 default-secondary-button ${buttonLoadingState(isLoading)}`}
|
||||
data-cy="query-run-button"
|
||||
disabled={isInDraft}
|
||||
{...(isInDraft && {
|
||||
'data-tooltip-id': 'query-header-btn-run',
|
||||
'data-tooltip-content': 'Publish the query to run',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={cx({
|
||||
invisible: isLoading,
|
||||
})}
|
||||
<span>
|
||||
<ToolTip message={shortcutDisplay} placement="bottom" trigger={['hover']} show={true} tooltipClassName="">
|
||||
<ButtonComponent
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)}
|
||||
leadingIcon="play01"
|
||||
disabled={isInDraft}
|
||||
isLoading={isLoading}
|
||||
className={isMac ? '!tw-w-[88px]' : '!tw-w-[120px]'}
|
||||
data-cy="query-run-button"
|
||||
>
|
||||
<Play width={14} fill="var(--indigo9)" viewBox="0 0 14 14" />
|
||||
</span>
|
||||
<span className="query-manager-btn-name">{isLoading ? ' ' : 'Run'}</span>
|
||||
</button>
|
||||
{isInDraft && <Tooltip id="query-header-btn-run" className="tooltip" />}
|
||||
Run
|
||||
<span className="query-manager-btn-shortcut">{isMac ? '⌘↩' : 'Ctrl+Enter'}</span>
|
||||
</ButtonComponent>
|
||||
</ToolTip>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
@ -287,20 +279,22 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => {
|
|||
: true;
|
||||
const isPreviewQueryLoading = useStore((state) => state.queryPanel.isPreviewQueryLoading);
|
||||
const { t } = useTranslation();
|
||||
const isMac = typeof navigator !== 'undefined' && navigator?.userAgent?.toLowerCase().includes('mac');
|
||||
|
||||
const shortcutDisplay = `Preview query ${isMac ? '⌘⇧↩' : 'Ctrl+Shift+Enter'}`;
|
||||
return (
|
||||
<button
|
||||
disabled={!hasPermissions}
|
||||
onClick={onClick}
|
||||
className={cx(`default-tertiary-button ${buttonLoadingState(isPreviewQueryLoading)} `, {
|
||||
disabled: !hasPermissions,
|
||||
})}
|
||||
data-cy={'query-preview-button'}
|
||||
>
|
||||
<span className="query-preview-svg d-flex align-items-center query-icon-wrapper">
|
||||
<Eye1 width={14} fill="var(--slate9)" />
|
||||
</span>
|
||||
<span>{t('editor.queryManager.preview', 'Preview')}</span>
|
||||
</button>
|
||||
<ToolTip message={shortcutDisplay} placement="bottom" trigger={['hover']} show={true} tooltipClassName="">
|
||||
<ButtonComponent
|
||||
size="medium"
|
||||
variant="outline"
|
||||
onClick={onClick}
|
||||
// className="!tw-w-[100px]"
|
||||
disabled={!hasPermissions}
|
||||
isLoading={isPreviewQueryLoading}
|
||||
data-cy={'query-preview-button'}
|
||||
>
|
||||
Preview
|
||||
</ButtonComponent>
|
||||
</ToolTip>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export const Transformation = ({ changeOption, options, darkMode, queryId, rende
|
|||
<br />
|
||||
<div className={`d-flex copilot-codehinter-wrap ${!enableTransformation && 'read-only-codehinter'}`}>
|
||||
<div className="col flex-grow-1">
|
||||
<div style={{ borderRadius: '6px', marginBottom: '20px', background: darkMode ? '#272822' : '#F8F9FA' }}>
|
||||
<div style={{ borderRadius: '6px', background: darkMode ? '#272822' : '#F8F9FA' }}>
|
||||
<div className="py-3 px-3 d-flex justify-content-between copilot-section-header">
|
||||
<Tab.Container
|
||||
activeKey={lang}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export default ({
|
|||
);
|
||||
})}
|
||||
{bodyToggle && (
|
||||
<div>
|
||||
<div className="rest-api-body-codehinter">
|
||||
<CodeHinter
|
||||
type="extendedSingleLine"
|
||||
initialValue={(rawBody || jsonBody) ?? ''} // If raw_body is not set, set initial value to legacy json_body if present
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const Runjs = (props) => {
|
|||
}, [props.options]);
|
||||
|
||||
return (
|
||||
<Card className="runjs-editor mb-3">
|
||||
<Card className="runjs-editor mb-3 !tw-mb-0">
|
||||
<CodeHinter
|
||||
renderCopilot={props.renderCopilot}
|
||||
type="multiline"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export class Runpy extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div className="runps-editor mb-3">
|
||||
<div className="runps-editor mb-3 !tw-mb-0">
|
||||
<CodeHinter
|
||||
renderCopilot={this.props.renderCopilot}
|
||||
type="multiline"
|
||||
|
|
|
|||
26
frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx
Normal file
26
frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const QueryKeyHooks = ({ children, isExpanded }) => {
|
||||
const runQueryOnShortcut = useStore((state) => state.queryPanel.runQueryOnShortcut);
|
||||
const previewQueryOnShortcut = useStore((state) => state.queryPanel.previewQueryOnShortcut);
|
||||
const moduleId = useModuleId();
|
||||
|
||||
useHotkeys(
|
||||
['mod+enter', 'mod+shift+enter'],
|
||||
(event, handler) => {
|
||||
if (handler.mod && handler.keys[0] === 'enter') {
|
||||
if (handler.shift) {
|
||||
previewQueryOnShortcut(moduleId);
|
||||
} else runQueryOnShortcut();
|
||||
}
|
||||
},
|
||||
{ enabled: isExpanded, enableOnFormTags: ['input'] }
|
||||
);
|
||||
|
||||
return <div className="row main-row">{children}</div>;
|
||||
};
|
||||
|
||||
export default QueryKeyHooks;
|
||||
|
|
@ -11,6 +11,7 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import SectionCollapse from '@/_ui/Icon/solidIcons/SectionCollapse';
|
||||
import SectionExpand from '@/_ui/Icon/solidIcons/SectionExpand';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import QueryKeyHooks from './QueryKeyHooks';
|
||||
|
||||
const MemoizedQueryDataPane = memo(QueryDataPane);
|
||||
const MemoizedQueryManager = memo(QueryManager);
|
||||
|
|
@ -193,14 +194,14 @@ export const QueryPanel = ({ darkMode }) => {
|
|||
}}
|
||||
>
|
||||
{isExpanded && (
|
||||
<div className="row main-row">
|
||||
<QueryKeyHooks isExpanded={isExpanded}>
|
||||
<MemoizedQueryDataPane darkMode={darkMode} />
|
||||
<div className="query-definition-pane-wrapper">
|
||||
<div className="query-definition-pane">
|
||||
<MemoizedQueryManager darkMode={darkMode} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</QueryKeyHooks>
|
||||
)}
|
||||
</div>
|
||||
<Tooltip id="tooltip-for-query-panel-footer-btn" className="tooltip" />
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const Form = ({
|
|||
|
||||
const { id } = component;
|
||||
const newOptions = [{ name: 'None', value: 'none' }];
|
||||
|
||||
Object.entries(allComponents).forEach(([componentId, _component]) => {
|
||||
const validParent =
|
||||
_component.component.parent === id ||
|
||||
|
|
@ -52,6 +53,19 @@ export const Form = ({
|
|||
|
||||
tempComponentMeta.properties.buttonToSubmit.options = newOptions;
|
||||
|
||||
// Hide header footer if custom schema is turned on
|
||||
|
||||
if (component.component.definition.properties.advanced.value === '{{true}}') {
|
||||
component.component.properties.showHeader = {
|
||||
...component.component.properties.headerHeight,
|
||||
isHidden: true,
|
||||
};
|
||||
component.component.properties.showFooter = {
|
||||
...component.component.properties.headerHeight,
|
||||
isHidden: true,
|
||||
};
|
||||
}
|
||||
|
||||
const accordionItems = baseComponentProperties(
|
||||
properties,
|
||||
events,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,19 @@ export const buttonGroupConfig = {
|
|||
defaultValue: 'left',
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selected: [1],
|
||||
|
|
@ -162,6 +175,7 @@ export const buttonGroupConfig = {
|
|||
borderRadius: { value: '{{4}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
selectedTextColor: { value: '#FFFFFF' },
|
||||
padding: { value: 'default' },
|
||||
selectedBackgroundColor: { value: 'var(--primary-brand)' },
|
||||
alignment: { value: 'left' },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const checkboxConfig = {
|
|||
],
|
||||
accordian: 'label',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -189,6 +203,7 @@ export const checkboxConfig = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
validation: {
|
||||
mandatory: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -28,6 +28,19 @@ export const colorPickerConfig = {
|
|||
},
|
||||
styles: {
|
||||
visibility: { type: 'toggle', displayName: 'Visibility' },
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selectedColorHex: '#000000',
|
||||
|
|
@ -47,6 +60,7 @@ export const colorPickerConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ export const dropdownV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -314,6 +326,8 @@ export const dropdownV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -78,6 +78,29 @@ export const iconConfig = {
|
|||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'Icon',
|
||||
},
|
||||
boxShadow: {
|
||||
type: 'boxShadow',
|
||||
displayName: 'Box shadow',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: '0px 0px 0px 0px #00000040',
|
||||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
},
|
||||
exposedVariables: {},
|
||||
actions: [
|
||||
|
|
@ -116,6 +139,8 @@ export const iconConfig = {
|
|||
styles: {
|
||||
iconColor: { value: '#000' },
|
||||
iconAlign: { value: 'center' },
|
||||
padding: { value: 'default' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000040' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export const modalV2Config = {
|
|||
component: 'ModalV2',
|
||||
defaultSize: {
|
||||
width: 10,
|
||||
height: 34,
|
||||
height: 40,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
|
|
@ -137,7 +137,7 @@ export const modalV2Config = {
|
|||
layout: {
|
||||
top: 24,
|
||||
left: 22,
|
||||
height: 36,
|
||||
height: 40,
|
||||
},
|
||||
displayName: 'ModalFooterCancel',
|
||||
properties: ['text'],
|
||||
|
|
@ -154,7 +154,7 @@ export const modalV2Config = {
|
|||
layout: {
|
||||
top: 24,
|
||||
left: 32,
|
||||
height: 36,
|
||||
height: 40,
|
||||
},
|
||||
displayName: 'ModalFooterConfirm',
|
||||
properties: ['text'],
|
||||
|
|
|
|||
|
|
@ -142,6 +142,18 @@ export const multiselectV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -327,6 +339,8 @@ export const multiselectV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ export const rangeSliderConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: null,
|
||||
|
|
@ -111,6 +124,7 @@ export const rangeSliderConfig = {
|
|||
handleColor: { value: '' },
|
||||
trackColor: { value: 'var(--primary-brand)' },
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -89,6 +89,19 @@ export const starratingConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: 0,
|
||||
|
|
@ -112,6 +125,7 @@ export const starratingConfig = {
|
|||
labelColor: { value: '' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,19 @@ export const tagsConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
alignment: {
|
||||
type: 'alignButtons',
|
||||
displayName: 'Alignment',
|
||||
|
|
@ -62,6 +75,7 @@ export const tagsConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
alignment: { value: 'left' },
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const toggleSwitchV2Config = {
|
|||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
|
||||
accordian: 'switch',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -187,6 +201,7 @@ export const toggleSwitchV2Config = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ export const Form = function Form(props) {
|
|||
if (e.target.className === 'real-canvas') onComponentClick(id, component);
|
||||
}} //Hack, should find a better solution - to prevent losing z index+1 when container element is clicked
|
||||
>
|
||||
{showHeader && (
|
||||
{!advanced && showHeader && (
|
||||
<div style={formHeader} className="wj-form-header">
|
||||
<SubContainer
|
||||
id={`${id}-header`}
|
||||
|
|
@ -381,7 +381,7 @@ export const Form = function Form(props) {
|
|||
</fieldset>
|
||||
)}
|
||||
</div>
|
||||
{showFooter && (
|
||||
{!advanced && showFooter && (
|
||||
<div className="jet-form-footer wj-form-footer" style={formFooter}>
|
||||
<SubContainer
|
||||
id={`${id}-footer`}
|
||||
|
|
|
|||
|
|
@ -38,3 +38,16 @@
|
|||
box-sizing: content-box;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.jet-container.jet-container-json-form {
|
||||
padding: 0px;
|
||||
|
||||
.wj-form-header::after,
|
||||
.wj-form-footer::after {
|
||||
left: -3px;
|
||||
right: -3px;
|
||||
}
|
||||
.jet-form-body fieldset {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ export const Modal = function Modal({
|
|||
|
||||
<Modal.Component
|
||||
show={showModal}
|
||||
contentClassName="modal-component"
|
||||
contentClassName="modal-component tj-modal--container"
|
||||
container={document.getElementsByClassName('real-canvas')[0]}
|
||||
size={size}
|
||||
keyboard={true}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const ModalWidget = ({ ...restProps }) => {
|
|||
return (
|
||||
<BootstrapModal
|
||||
{...restProps}
|
||||
contentClassName="modal-component tj-modal-widget-content"
|
||||
contentClassName="modal-component tj-modal--container tj-modal-widget-content"
|
||||
animation={true}
|
||||
onEscapeKeyDown={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ export const Header = memo(
|
|||
</div>
|
||||
</div>
|
||||
{showFilter && (
|
||||
<Filter table={table} darkMode={darkMode} setFilters={setFilters} setShowFilter={setShowFilter} />
|
||||
<Filter id={id} table={table} darkMode={darkMode} setFilters={setFilters} setShowFilter={setShowFilter} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { FilterFooter } from './FilterFooter';
|
|||
import { FilterHeader } from './FilterHeader';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
|
||||
export const Filter = memo(({ table, darkMode, setFilters, setShowFilter }) => {
|
||||
export const Filter = memo(({ id, table, darkMode, setFilters, setShowFilter }) => {
|
||||
const { t } = useTranslation();
|
||||
const [localFilters, setLocalFilters] = useState(table.getState().columnFilters);
|
||||
|
||||
|
|
@ -142,6 +142,7 @@ export const Filter = memo(({ table, darkMode, setFilters, setShowFilter }) => {
|
|||
<div className="card-body">
|
||||
{localFilters.map((filter, index) => (
|
||||
<FilterRow
|
||||
id={id}
|
||||
key={index}
|
||||
filter={filter}
|
||||
index={index}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Select from '@/_ui/Select';
|
||||
import { components } from 'react-select';
|
||||
import defaultStyles from '@/_ui/Select/styles';
|
||||
import { FILTER_OPTIONS } from './filterConstants';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
export const FilterRow = memo(
|
||||
({ filter, index, columns, darkMode, onColumnChange, onOperationChange, onValueChange, onRemove }) => {
|
||||
({ id, filter, index, columns, darkMode, onColumnChange, onOperationChange, onValueChange, onRemove }) => {
|
||||
const { t } = useTranslation();
|
||||
const isDragging = useStore((state) => state.draggingComponentId === id);
|
||||
|
||||
const selectStyles = (width) => {
|
||||
return {
|
||||
|
|
@ -15,6 +18,10 @@ export const FilterRow = memo(
|
|||
menuList: (base) => ({
|
||||
...base,
|
||||
}),
|
||||
menu: (base) => ({
|
||||
...base,
|
||||
display: isDragging ? 'none' : 'block',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -29,11 +36,13 @@ export const FilterRow = memo(
|
|||
value={filter.id}
|
||||
search={true}
|
||||
onChange={(value) => onColumnChange(index, value)}
|
||||
components={{ ValueContainer: CustomValueContainer }}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
className={`${darkMode ? 'select-search-dark' : 'select-search'} mb-0`}
|
||||
styles={selectStyles('100%')}
|
||||
useCustomStyles={true}
|
||||
darkMode={darkMode}
|
||||
openMenuOnFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<div data-cy={`select-operation-dropdown-${index}`} className="col" style={{ maxWidth: '180px' }}>
|
||||
|
|
@ -42,11 +51,13 @@ export const FilterRow = memo(
|
|||
value={filter.value.condition}
|
||||
search={true}
|
||||
onChange={(value) => onOperationChange(index, value)}
|
||||
components={{ ValueContainer: CustomValueContainer }}
|
||||
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
styles={selectStyles('100%')}
|
||||
useCustomStyles={true}
|
||||
darkMode={darkMode}
|
||||
openMenuOnFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
|
|
@ -74,3 +85,15 @@ export const FilterRow = memo(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
const CustomValueContainer = (props) => {
|
||||
const handleClick = (e) => {
|
||||
if (props.selectProps?.selectRef?.current) {
|
||||
props.selectProps.selectRef.current.focus();
|
||||
}
|
||||
if (props.innerProps?.onMouseDown) {
|
||||
props.innerProps.onMouseDown(e);
|
||||
}
|
||||
};
|
||||
return <components.ValueContainer {...props} innerProps={{ ...props.innerProps, onMouseDown: handleClick }} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,4 +36,27 @@ export const createCodeHinterSlice = (set, get) => ({
|
|||
setSuggestions({ appHints: suggestionList, jsHints: jsHints });
|
||||
},
|
||||
getSuggestions: () => get().suggestions,
|
||||
getServerSideGlobalSuggestions: (isInsideQueryManager) => {
|
||||
const isServerSideGlobalEnabled = !!get()?.license?.featureAccess?.serverSideGlobal;
|
||||
const serverHints = [];
|
||||
const hints = get().getSuggestions();
|
||||
if (isInsideQueryManager && isServerSideGlobalEnabled) {
|
||||
serverHints.push({ hint: 'globals.server', type: 'Object' });
|
||||
hints?.appHints?.forEach((appHint) => {
|
||||
if (appHint?.hint?.startsWith('globals.currentUser')) {
|
||||
const key = appHint?.hint?.replace('globals.currentUser', 'globals.server.currentUser');
|
||||
console.log({
|
||||
hint: key,
|
||||
type: appHint?.type,
|
||||
});
|
||||
serverHints.push({
|
||||
hint: key,
|
||||
type: appHint?.type,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return serverHints;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1092,5 +1092,23 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
isQuerySelected: (queryId) => {
|
||||
return get().queryPanel.selectedQuery?.id === queryId;
|
||||
},
|
||||
runQueryOnShortcut: () => {
|
||||
const { queryPanel } = get();
|
||||
const { runQuery, selectedQuery } = queryPanel;
|
||||
runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true);
|
||||
},
|
||||
previewQueryOnShortcut: (moduleId = 'canvas') => {
|
||||
const { queryPanel } = get();
|
||||
const { previewQuery, selectedQuery, selectedDataSource } = queryPanel;
|
||||
const query = {
|
||||
data_source_id: selectedDataSource.id === 'null' ? null : selectedDataSource.id,
|
||||
pluginId: selectedDataSource.pluginId,
|
||||
options: { ...selectedQuery?.options },
|
||||
kind: selectedDataSource.kind,
|
||||
name: selectedQuery?.name ?? '',
|
||||
id: selectedQuery?.id,
|
||||
};
|
||||
previewQuery(query, false, undefined, moduleId);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -161,8 +161,8 @@ export const ColorPicker = function ({
|
|||
: { display: 'none' };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ baseStyle, boxShadow }} className="form-control" data-cy={dataCy}>
|
||||
<div className="h-100">
|
||||
<div style={{ baseStyle, boxShadow, height: '100%' }} className="form-control" data-cy={dataCy}>
|
||||
<div
|
||||
className="d-flex h-100 justify-content-between align-items-center"
|
||||
onClick={() => setShowColorPicker(true)}
|
||||
|
|
|
|||
|
|
@ -1,87 +1,118 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { components } from 'react-select';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import Loader from '@/ToolJetUI/Loader/Loader';
|
||||
import './dropdownV2.scss';
|
||||
import { FormCheck } from 'react-bootstrap';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import cx from 'classnames';
|
||||
|
||||
const { MenuList } = components;
|
||||
|
||||
// This Menulist also used in MultiselectV2
|
||||
const CustomMenuList = ({ selectProps, ...props }) => {
|
||||
const {
|
||||
onInputChange,
|
||||
onMenuInputFocus,
|
||||
showAllOption,
|
||||
isSelectAllSelected,
|
||||
optionsLoadingState,
|
||||
darkMode,
|
||||
setSelected,
|
||||
setIsSelectAllSelected,
|
||||
fireEvent,
|
||||
inputValue,
|
||||
menuId,
|
||||
} = selectProps;
|
||||
const { onInputChange, onMenuInputFocus, optionsLoadingState, darkMode, inputValue, menuId, showSearchInput } =
|
||||
selectProps;
|
||||
|
||||
const handleSelectAll = (e) => {
|
||||
e.target.checked && fireEvent();
|
||||
if (e.target.checked) {
|
||||
setSelected(props.options);
|
||||
} else {
|
||||
setSelected([]);
|
||||
const parentRef = useRef(null);
|
||||
const virtualizer = useVirtualizer({
|
||||
count: props?.children?.length || 0,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 40,
|
||||
overscan: 15,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const searchInput = document.querySelector('.dropdown-multiselect-widget-search-box');
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
setIsSelectAllSelected(e.target.checked);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`dropdown-multiselect-widget-custom-menu-list-${menuId}`}
|
||||
className={cx('dropdown-multiselect-widget-custom-menu-list', { 'theme-dark dark-theme': darkMode })}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onTouchEnd={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="dropdown-multiselect-widget-search-box-wrapper">
|
||||
<span>
|
||||
<SolidIcon name="search01" width="14" />
|
||||
</span>
|
||||
<input
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
onInputChange(e.currentTarget.value, {
|
||||
action: 'input-change',
|
||||
})
|
||||
}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onFocus={onMenuInputFocus}
|
||||
placeholder="Search"
|
||||
className="dropdown-multiselect-widget-search-box"
|
||||
/>
|
||||
</div>
|
||||
{showAllOption && !optionsLoadingState && (
|
||||
<label htmlFor="select-all-checkbox" className="multiselect-custom-menulist-select-all">
|
||||
<FormCheck id="select-all-checkbox" checked={isSelectAllSelected} onChange={handleSelectAll} />
|
||||
<span style={{ marginLeft: '4px' }}>Select all</span>
|
||||
</label>
|
||||
{showSearchInput && (
|
||||
<div className="dropdown-multiselect-widget-search-box-wrapper">
|
||||
<span>
|
||||
<SolidIcon name="search01" width="14" />
|
||||
</span>
|
||||
<input
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
onInputChange(e.currentTarget.value, {
|
||||
action: 'input-change',
|
||||
})
|
||||
}
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
e.target.focus();
|
||||
}}
|
||||
onFocus={onMenuInputFocus}
|
||||
placeholder="Search"
|
||||
className="dropdown-multiselect-widget-search-box"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<MenuList {...props} selectProps={selectProps}>
|
||||
{optionsLoadingState ? (
|
||||
<div class="text-center py-4" style={{ minHeight: '188px' }}>
|
||||
<Loader style={{ zIndex: 3, position: 'absolute' }} width="36" />
|
||||
{!optionsLoadingState && (
|
||||
<div
|
||||
ref={parentRef}
|
||||
className="dropdown-multiselect-widget-custom-menu-list-body"
|
||||
style={{
|
||||
maxHeight: selectProps.maxMenuHeight || 300,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: `${virtualizer.getTotalSize() || 38}px`,
|
||||
position: 'relative',
|
||||
marginTop: '5px',
|
||||
}}
|
||||
>
|
||||
{!virtualizer.getTotalSize() && props.children}
|
||||
{virtualizer.getVirtualItems().map((virtualItem) => {
|
||||
const option = props.options[virtualItem.index];
|
||||
const child = props.children[virtualItem.index];
|
||||
const isSelectAll = option?.value === 'multiselect-custom-menulist-select-all';
|
||||
return (
|
||||
<div
|
||||
key={option.value}
|
||||
style={{
|
||||
position: isSelectAll ? 'sticky' : 'absolute',
|
||||
width: '100%',
|
||||
top: 0,
|
||||
zIndex: isSelectAll && 10,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
data-index={virtualItem.index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<MenuList {...props} selectProps={selectProps}>
|
||||
<div>{child}</div>
|
||||
</MenuList>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</MenuList>
|
||||
</div>
|
||||
)}
|
||||
{optionsLoadingState && (
|
||||
<div className="text-center py-4" style={{ minHeight: '188px' }}>
|
||||
<Loader style={{ zIndex: 3, position: 'absolute' }} width="36" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,17 @@ import { highlightText } from './utils';
|
|||
|
||||
const CustomOption = (props) => {
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<components.Option
|
||||
{...props}
|
||||
innerProps={{
|
||||
...props.innerProps,
|
||||
onTouchEnd: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.selectOption(props.data);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="cursor-pointer">
|
||||
{props.isSelected && (
|
||||
<span style={{ maxHeight: '20px', marginRight: '8px', marginLeft: '-28px' }}>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import Label from '@/_ui/Label';
|
|||
import cx from 'classnames';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor, sortArray } from './utils';
|
||||
import { isMobileDevice } from '@/_helpers/appUtils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
const { DropdownIndicator, ClearIndicator } = components;
|
||||
const INDICATOR_CONTAINER_WIDTH = 60;
|
||||
|
|
@ -69,6 +68,8 @@ export const DropdownV2 = ({
|
|||
disabledState,
|
||||
optionsLoadingState,
|
||||
sort,
|
||||
showClearBtn,
|
||||
showSearchInput,
|
||||
} = properties;
|
||||
const {
|
||||
selectedTextColor,
|
||||
|
|
@ -104,8 +105,6 @@ export const DropdownV2 = ({
|
|||
const [isDropdownDisabled, setIsDropdownDisabled] = useState(disabledState);
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const [userInteracted, setUserInteracted] = useState(false);
|
||||
const currentMode = useStore((state) => state.currentMode);
|
||||
const isEditor = currentMode === 'edit';
|
||||
|
||||
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
|
||||
const labelRef = useRef();
|
||||
|
|
@ -173,12 +172,43 @@ export const DropdownV2 = ({
|
|||
setExposedVariable('isValid', validationStatus?.isValid);
|
||||
};
|
||||
|
||||
const handleClickInEditor = (e) => {
|
||||
if (e.target.className.includes('clear-indicator') || isMenuOpen) return;
|
||||
e.stopPropagation();
|
||||
selectRef.current?.onControlMouseDown(e);
|
||||
const handleClickInsideSelect = () => {
|
||||
if (isDropdownDisabled || isDropdownLoading) return;
|
||||
if (isMenuOpen) {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
} else {
|
||||
setIsMenuOpen(true);
|
||||
fireEvent('onFocus');
|
||||
if (!showSearchInput) {
|
||||
selectRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickOutsideSelect = (event) => {
|
||||
const menu = document.querySelector(`._tooljet-${componentName}`);
|
||||
if (
|
||||
isMenuOpen &&
|
||||
menu &&
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target) &&
|
||||
!menu.contains(event.target)
|
||||
) {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutsideSelect);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutsideSelect);
|
||||
};
|
||||
}, [isMenuOpen, componentName]);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(findDefaultItem(advanced ? schema : options));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -331,8 +361,8 @@ export const DropdownV2 = ({
|
|||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isDropdownDisabled || isDropdownLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
maxWidth:
|
||||
ref?.current?.offsetWidth -
|
||||
(iconVisibility ? INDICATOR_CONTAINER_WIDTH + ICON_WIDTH : INDICATOR_CONTAINER_WIDTH),
|
||||
|
|
@ -373,8 +403,8 @@ export const DropdownV2 = ({
|
|||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isDropdownDisabled || isDropdownLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
borderRadius: _state.isFocused && '8px',
|
||||
padding: '8px 6px 8px 38px',
|
||||
'&:hover': {
|
||||
|
|
@ -386,7 +416,7 @@ export const DropdownV2 = ({
|
|||
}),
|
||||
menuList: (provided) => ({
|
||||
...provided,
|
||||
padding: '8px',
|
||||
padding: '0 8px',
|
||||
borderRadius: '8px',
|
||||
// this is needed otherwise :active state doesn't look nice, gap is required
|
||||
display: 'flex',
|
||||
|
|
@ -410,8 +440,8 @@ export const DropdownV2 = ({
|
|||
data-cy={`label-${String(componentName).toLowerCase()} `}
|
||||
className={cx('dropdown-widget', 'd-flex', {
|
||||
[alignment === 'top' &&
|
||||
((labelWidth != 0 && label?.length != 0) ||
|
||||
(labelAutoWidth && labelWidth == 0 && label && label?.length != 0))
|
||||
((labelWidth != 0 && label?.length != 0) ||
|
||||
(labelAutoWidth && labelWidth == 0 && label && label?.length != 0))
|
||||
? 'flex-column'
|
||||
: 'align-items-center']: true,
|
||||
'flex-row-reverse': direction === 'right' && alignment === 'side',
|
||||
|
|
@ -446,7 +476,8 @@ export const DropdownV2 = ({
|
|||
<div
|
||||
className="w-100 px-0 h-100 dropdownV2-widget"
|
||||
ref={ref}
|
||||
onMouseDownCapture={isEditor && handleClickInEditor}
|
||||
onClick={handleClickInsideSelect}
|
||||
onTouchEnd={handleClickInsideSelect}
|
||||
>
|
||||
<Select
|
||||
ref={selectRef}
|
||||
|
|
@ -456,17 +487,21 @@ export const DropdownV2 = ({
|
|||
onChange={(selectedOption, actionProps) => {
|
||||
if (actionProps.action === 'clear') {
|
||||
setInputValue(null);
|
||||
fireEvent('onSelect');
|
||||
}
|
||||
if (actionProps.action === 'select-option') {
|
||||
setInputValue(selectedOption.value);
|
||||
fireEvent('onSelect');
|
||||
if (currentValue === selectedOption.value) {
|
||||
setInputValue(null);
|
||||
} else setInputValue(selectedOption.value);
|
||||
}
|
||||
fireEvent('onSelect');
|
||||
setSearchInputValue('');
|
||||
setIsMenuOpen(false);
|
||||
setUserInteracted(true);
|
||||
}}
|
||||
options={selectOptions}
|
||||
styles={customStyles}
|
||||
isLoading={isDropdownLoading}
|
||||
showSearchInput={showSearchInput}
|
||||
onInputChange={onSearchTextChange}
|
||||
inputValue={searchInputValue}
|
||||
placeholder={placeholder}
|
||||
|
|
@ -477,7 +512,7 @@ export const DropdownV2 = ({
|
|||
Option: CustomOption,
|
||||
LoadingIndicator: () => <Loader style={{ right: '11px', zIndex: 3, position: 'absolute' }} width="16" />,
|
||||
DropdownIndicator: isDropdownLoading ? () => null : CustomDropdownIndicator,
|
||||
ClearIndicator: CustomClearIndicator,
|
||||
ClearIndicator: showClearBtn ? CustomClearIndicator : () => null,
|
||||
}}
|
||||
isClearable
|
||||
tabSelectsValue={false}
|
||||
|
|
@ -488,24 +523,20 @@ export const DropdownV2 = ({
|
|||
darkMode={darkMode}
|
||||
optionsLoadingState={optionsLoadingState && advanced}
|
||||
menuPlacement="auto"
|
||||
onMenuOpen={() => {
|
||||
setIsMenuOpen(true);
|
||||
fireEvent('onFocus');
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !isMenuOpen) {
|
||||
if (e.key === 'Enter' && !isMenuOpen && !isDropdownLoading) {
|
||||
setIsMenuOpen(true);
|
||||
fireEvent('onFocus');
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Escape' && isMenuOpen) {
|
||||
setIsMenuOpen(false);
|
||||
fireEvent('onBlur');
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
// This is not setting minheight, required to help calculate menuPlacement by providing fixed height upfront before rendering (required in the case of modal)
|
||||
minMenuHeight={300}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
.dropdown-multiselect-widget-custom-menu-list-body {
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
padding: 0 0 5px;
|
||||
background-color: var(--surfaces-surface-01) !important;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ export const Icon = ({
|
|||
}) => {
|
||||
const isInitialRender = useRef(true);
|
||||
const { icon, loadingState, disabledState } = properties;
|
||||
const { iconAlign, iconColor } = styles;
|
||||
const { iconAlign, iconColor, boxShadow } = styles;
|
||||
// eslint-disable-next-line import/namespace
|
||||
const IconElement = Icons[icon];
|
||||
|
||||
|
|
@ -84,10 +84,10 @@ export const Icon = ({
|
|||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={cx('icon-widget', { 'd-none': !visibility }, { 'cursor-pointer': false })}
|
||||
className={cx('icon-widget h-100', { 'd-none': !visibility }, { 'cursor-pointer': false })}
|
||||
data-cy={dataCy}
|
||||
data-disabled={isDisabled}
|
||||
style={{ textAlign: iconAlign }}
|
||||
style={{ textAlign: iconAlign, boxShadow }}
|
||||
onMouseEnter={(event) => {
|
||||
event.stopPropagation();
|
||||
fireEvent('onHover');
|
||||
|
|
@ -97,7 +97,7 @@ export const Icon = ({
|
|||
color={color}
|
||||
style={{
|
||||
width: height < width ? 'auto' : width,
|
||||
height: height < width ? height : 'auto',
|
||||
height: height < width ? '100%' : 'auto',
|
||||
color: iconColor,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ export const Link = ({ height, properties, styles, fireEvent, setExposedVariable
|
|||
const computedStyles = {
|
||||
display: 'flex',
|
||||
alignItems: verticalAlignment === 'top' ? 'flex-start' : verticalAlignment === 'center' ? 'center' : 'flex-end',
|
||||
justifyContent:
|
||||
horizontalAlignment === 'left' ? 'flex-start' : horizontalAlignment === 'center' ? 'center' : 'flex-end',
|
||||
textAlign: horizontalAlignment === 'left' ? 'left' : horizontalAlignment === 'center' ? 'center' : 'right',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
boxShadow,
|
||||
|
|
@ -113,10 +112,20 @@ export const Link = ({ height, properties, styles, fireEvent, setExposedVariable
|
|||
onMouseOver={() => {
|
||||
fireEvent('onHover');
|
||||
}}
|
||||
style={{ color: textColor, fontSize: textSize, cursor: isDisabled ? 'not-allowed' : 'pointer' }}
|
||||
style={{ width: '100%' }}
|
||||
ref={clickRef}
|
||||
>
|
||||
<span className="d-flex justify-content-center">
|
||||
<span
|
||||
className="d-flex"
|
||||
style={{
|
||||
fontSize: textSize,
|
||||
cursor: 'auto',
|
||||
justifyContent:
|
||||
horizontalAlignment === 'left' ? 'flex-start' : horizontalAlignment === 'center' ? 'center' : 'flex-end',
|
||||
color: textColor,
|
||||
paddingBottom: verticalAlignment === 'bottom' ? '1px' : '0px',
|
||||
}}
|
||||
>
|
||||
{iconVisibility && (
|
||||
<IconElement
|
||||
style={{
|
||||
|
|
@ -130,7 +139,7 @@ export const Link = ({ height, properties, styles, fireEvent, setExposedVariable
|
|||
stroke={1.5}
|
||||
/>
|
||||
)}
|
||||
{linkTextState}
|
||||
<span className="link-text">{linkTextState}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
.link-widget {
|
||||
a {
|
||||
text-underline-offset: 32%; // Adds space between text and underline
|
||||
&:hover {
|
||||
color: var(--link-hover-color) !important;
|
||||
}
|
||||
text-decoration: none !important;
|
||||
pointer-events: none;
|
||||
cursor: none !important;
|
||||
|
||||
.link-text {
|
||||
pointer-events: all;
|
||||
text-underline-offset: 32%; // Adds space between text and underline
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--link-hover-color) !important;
|
||||
color: var(--link-hover-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,23 @@ import { highlightText } from '../DropdownV2/utils';
|
|||
|
||||
const CustomOption = (props) => {
|
||||
return (
|
||||
<Option {...props}>
|
||||
<Option
|
||||
{...props}
|
||||
innerProps={{
|
||||
...props.innerProps,
|
||||
onTouchEnd: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.selectOption(props.data);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="d-flex multiselct-widget-option">
|
||||
<FormCheck checked={props.isSelected} disabled={props?.isDisabled} />
|
||||
<span style={{ marginLeft: '5px' }}>
|
||||
{highlightText(props.label?.toString(), props.selectProps.inputValue)}
|
||||
{props.label?.includes('Select all')
|
||||
? 'Select all'
|
||||
: highlightText(props.label?.toString(), props.selectProps.inputValue)}
|
||||
</span>
|
||||
</div>
|
||||
</Option>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const CustomValueContainer = ({ children, ...props }) => {
|
|||
{/* Rendering children except Placeholder component to preserve the default behavior of react-select like focus
|
||||
handling */}
|
||||
{React.Children.map(children, (child) => {
|
||||
if (child.type !== Placeholder) {
|
||||
if (child?.type !== Placeholder) {
|
||||
return child;
|
||||
}
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import Label from '@/_ui/Label';
|
|||
const tinycolor = require('tinycolor2');
|
||||
import { CustomDropdownIndicator, CustomClearIndicator } from '../DropdownV2/DropdownV2';
|
||||
import { getInputBackgroundColor, getInputBorderColor, getInputFocusedColor, sortArray } from '../DropdownV2/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
export const MultiselectV2 = ({
|
||||
id,
|
||||
|
|
@ -38,6 +37,8 @@ export const MultiselectV2 = ({
|
|||
loadingState: multiSelectLoadingState,
|
||||
optionsLoadingState,
|
||||
sort,
|
||||
showClearBtn,
|
||||
showSearchInput,
|
||||
} = properties;
|
||||
const {
|
||||
selectedTextColor,
|
||||
|
|
@ -73,12 +74,9 @@ export const MultiselectV2 = ({
|
|||
const [visibility, setVisibility] = useState(properties.visibility);
|
||||
const [isMultiSelectLoading, setIsMultiSelectLoading] = useState(multiSelectLoadingState);
|
||||
const [isMultiSelectDisabled, setIsMultiSelectDisabled] = useState(disabledState);
|
||||
const [isSelectAllSelected, setIsSelectAllSelected] = useState(false);
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const _height = padding === 'default' ? `${height}px` : `${height + 4}px`;
|
||||
const [userInteracted, setUserInteracted] = useState(false);
|
||||
const currentMode = useStore((state) => state.currentMode);
|
||||
const isEditor = currentMode === 'edit';
|
||||
|
||||
const [isMultiselectOpen, setIsMultiselectOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
|
|
@ -93,18 +91,29 @@ export const MultiselectV2 = ({
|
|||
const _options = advanced ? schema : options;
|
||||
let _selectOptions = Array.isArray(_options)
|
||||
? _options
|
||||
.filter((data) => data?.visible ?? true)
|
||||
.map((data) => ({
|
||||
...data,
|
||||
label: data?.label,
|
||||
value: data?.value,
|
||||
isDisabled: data?.disable ?? false,
|
||||
}))
|
||||
.filter((data) => data?.visible ?? true)
|
||||
.map((data) => ({
|
||||
...data,
|
||||
label: data?.label,
|
||||
value: data?.value,
|
||||
isDisabled: data?.disable ?? false,
|
||||
}))
|
||||
: [];
|
||||
return sortArray(_selectOptions, sort);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [advanced, JSON.stringify(schema), JSON.stringify(options), sort]);
|
||||
|
||||
const modifiedSelectOptions = useMemo(() => {
|
||||
// Adding select all option dynamically to the options
|
||||
if (showAllOption && !optionsLoadingState) {
|
||||
return [
|
||||
// Appended search input value so that it is always visible
|
||||
{ label: `Select all ${searchInputValue}`, value: 'multiselect-custom-menulist-select-all' },
|
||||
...selectOptions,
|
||||
];
|
||||
} else return selectOptions;
|
||||
}, [showAllOption, JSON.stringify(selectOptions), optionsLoadingState, searchInputValue]);
|
||||
|
||||
function findDefaultItem(value, isAdvanced, isDefault) {
|
||||
if (isAdvanced) {
|
||||
const foundItem = Array.isArray(schema) ? schema.filter((item) => item?.visible && item?.default) : [];
|
||||
|
|
@ -129,8 +138,28 @@ export const MultiselectV2 = ({
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const onChangeHandler = (items, action) => {
|
||||
setInputValue(items);
|
||||
const SELECT_ALL = 'multiselect-custom-menulist-select-all';
|
||||
|
||||
if (action.option?.value === SELECT_ALL) {
|
||||
// Case 1 - If select all is selected
|
||||
if (action.action === 'select-option') {
|
||||
setInputValue(modifiedSelectOptions);
|
||||
} else {
|
||||
setInputValue([]);
|
||||
}
|
||||
} else if (items?.some((item) => item.value === SELECT_ALL)) {
|
||||
// Case 2 - If select all is not selected but selected options include select all
|
||||
setInputValue(items.filter((item) => item.value !== SELECT_ALL));
|
||||
} else if (selectOptions?.length === items?.length) {
|
||||
// Case 3 - If all options are selected except select all
|
||||
setInputValue(modifiedSelectOptions);
|
||||
} else {
|
||||
// Case 4 - Normal selection
|
||||
setInputValue(items);
|
||||
}
|
||||
|
||||
fireEvent('onSelect');
|
||||
setUserInteracted(true);
|
||||
};
|
||||
|
|
@ -270,26 +299,34 @@ export const MultiselectV2 = ({
|
|||
fireEvent('onSearchTextChanged');
|
||||
}
|
||||
};
|
||||
const handleClickOutside = (event) => {
|
||||
const handleClickOutsideSelect = (event) => {
|
||||
let menu = document.getElementById(`dropdown-multiselect-widget-custom-menu-list-${id}`);
|
||||
if (
|
||||
isMultiselectOpen &&
|
||||
multiselectRef.current &&
|
||||
!multiselectRef.current.contains(event.target) &&
|
||||
menu &&
|
||||
!menu.contains(event.target)
|
||||
) {
|
||||
if (isMultiselectOpen) {
|
||||
fireEvent('onBlur');
|
||||
setIsMultiselectOpen(false);
|
||||
setSearchInputValue('');
|
||||
}
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickInEditor = (e) => {
|
||||
if (e.target.className.includes('clear-indicator') || isMultiselectOpen) return;
|
||||
e.stopPropagation();
|
||||
selectRef.current?.onControlMouseDown(e);
|
||||
const handleClickInsideSelect = () => {
|
||||
if (isMultiSelectDisabled || isMultiSelectLoading) return;
|
||||
if (isMultiselectOpen) {
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
setSearchInputValue('');
|
||||
} else {
|
||||
setIsMultiselectOpen(true);
|
||||
fireEvent('onFocus');
|
||||
if (!showSearchInput) {
|
||||
selectRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setInputValue = (values) => {
|
||||
|
|
@ -304,20 +341,11 @@ export const MultiselectV2 = ({
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside, { capture: true });
|
||||
document.addEventListener('mousedown', handleClickOutsideSelect, { capture: true });
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside, { capture: true });
|
||||
document.removeEventListener('mousedown', handleClickOutsideSelect, { capture: true });
|
||||
};
|
||||
}, [isMultiselectOpen]);
|
||||
|
||||
// Handle Select all logic
|
||||
useEffect(() => {
|
||||
if (selectOptions?.length === selected?.length) {
|
||||
setIsSelectAllSelected(true);
|
||||
} else {
|
||||
setIsSelectAllSelected(false);
|
||||
}
|
||||
}, [selectOptions, selected]);
|
||||
}, [isMultiselectOpen, componentName]);
|
||||
|
||||
const customStyles = {
|
||||
container: (base) => ({
|
||||
|
|
@ -365,8 +393,8 @@ export const MultiselectV2 = ({
|
|||
selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isMultiSelectLoading || isMultiSelectDisabled
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
}),
|
||||
|
||||
input: (provided, _state) => ({
|
||||
|
|
@ -401,10 +429,10 @@ export const MultiselectV2 = ({
|
|||
color: _state.isDisabled
|
||||
? 'var(_--text-disbled)'
|
||||
: selectedTextColor !== '#1B1F24'
|
||||
? selectedTextColor
|
||||
: isMultiSelectDisabled || isMultiSelectLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
? selectedTextColor
|
||||
: isMultiSelectDisabled || isMultiSelectLoading
|
||||
? 'var(--text-disabled)'
|
||||
: 'var(--text-primary)',
|
||||
borderRadius: _state.isFocused && '8px',
|
||||
padding: '8px 6px 8px 12px',
|
||||
'&:hover': {
|
||||
|
|
@ -415,7 +443,7 @@ export const MultiselectV2 = ({
|
|||
}),
|
||||
menuList: (provided) => ({
|
||||
...provided,
|
||||
padding: '4px',
|
||||
padding: '0 4px',
|
||||
// this is needed otherwise :active state doesn't look nice, gap is required
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
|
@ -426,6 +454,7 @@ export const MultiselectV2 = ({
|
|||
menu: (provided) => ({
|
||||
...provided,
|
||||
marginTop: '5px',
|
||||
borderRadius: '8px',
|
||||
}),
|
||||
};
|
||||
const _width = (labelWidth / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
|
||||
|
|
@ -436,7 +465,7 @@ export const MultiselectV2 = ({
|
|||
data-cy={`label-${String(componentName).toLowerCase()} `}
|
||||
className={cx('multiselect-widget', 'd-flex', {
|
||||
[alignment === 'top' &&
|
||||
((labelWidth != 0 && label?.length != 0) || (auto && labelWidth == 0 && label && label?.length != 0))
|
||||
((labelWidth != 0 && label?.length != 0) || (auto && labelWidth == 0 && label && label?.length != 0))
|
||||
? 'flex-column'
|
||||
: 'align-items-center']: true,
|
||||
'flex-row-reverse': direction === 'right' && alignment === 'side',
|
||||
|
|
@ -468,17 +497,18 @@ export const MultiselectV2 = ({
|
|||
_width={_width}
|
||||
top={'1px'}
|
||||
/>
|
||||
<div className="w-100 px-0 h-100" onMouseDownCapture={isEditor && handleClickInEditor}>
|
||||
<div className="w-100 px-0 h-100" onClick={handleClickInsideSelect} onTouchEnd={handleClickInsideSelect}>
|
||||
<Select
|
||||
ref={selectRef}
|
||||
menuId={id}
|
||||
isDisabled={isMultiSelectDisabled}
|
||||
value={selected}
|
||||
onChange={onChangeHandler}
|
||||
options={selectOptions}
|
||||
options={modifiedSelectOptions}
|
||||
styles={customStyles}
|
||||
// Only show loading when dynamic options are enabled
|
||||
isLoading={isMultiSelectLoading}
|
||||
showSearchInput={showSearchInput}
|
||||
onInputChange={onSearchTextChange}
|
||||
inputValue={searchInputValue}
|
||||
menuIsOpen={isMultiselectOpen}
|
||||
|
|
@ -488,7 +518,7 @@ export const MultiselectV2 = ({
|
|||
ValueContainer: CustomValueContainer,
|
||||
Option: CustomOption,
|
||||
LoadingIndicator: () => <Loader style={{ right: '11px', zIndex: 3, position: 'absolute' }} width="16" />,
|
||||
ClearIndicator: CustomClearIndicator,
|
||||
ClearIndicator: showClearBtn ? CustomClearIndicator : () => null,
|
||||
DropdownIndicator: isMultiSelectLoading ? () => null : CustomDropdownIndicator,
|
||||
}}
|
||||
isClearable
|
||||
|
|
@ -498,21 +528,15 @@ export const MultiselectV2 = ({
|
|||
tabSelectsValue={false}
|
||||
controlShouldRenderValue={false}
|
||||
isSearchable={false}
|
||||
onMenuOpen={() => {
|
||||
setIsMultiselectOpen(true);
|
||||
fireEvent('onFocus');
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !isMultiselectOpen) {
|
||||
if (e.key === 'Enter' && !isMultiselectOpen && !isMultiSelectLoading) {
|
||||
setIsMultiselectOpen(true);
|
||||
fireEvent('onFocus');
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Escape' && isMultiselectOpen) {
|
||||
setIsMultiselectOpen(false);
|
||||
fireEvent('onBlur');
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
|
|
@ -520,21 +544,13 @@ export const MultiselectV2 = ({
|
|||
icon={icon}
|
||||
doShowIcon={iconVisibility}
|
||||
containerRef={valueContainerRef}
|
||||
showAllOption={showAllOption}
|
||||
isSelectAllSelected={isSelectAllSelected}
|
||||
setIsSelectAllSelected={(value) => {
|
||||
setIsSelectAllSelected(value);
|
||||
if (!value) {
|
||||
fireEvent('onSelect');
|
||||
}
|
||||
}}
|
||||
setSelected={setInputValue}
|
||||
iconColor={iconColor}
|
||||
optionsLoadingState={optionsLoadingState && advanced}
|
||||
darkMode={darkMode}
|
||||
fireEvent={() => fireEvent('onSelect')}
|
||||
menuPlacement="auto"
|
||||
menuPortalTarget={document.body}
|
||||
// This is not setting minheight, required to help calculate menuPlacement by providing fixed height upfront before rendering (required in the case of modal)
|
||||
minMenuHeight={300}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -123,6 +123,19 @@ export const buttonGroupConfig = {
|
|||
defaultValue: '#007bff',
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selected: [1],
|
||||
|
|
@ -148,6 +161,7 @@ export const buttonGroupConfig = {
|
|||
disabledState: { value: '{{false}}' },
|
||||
selectedTextColor: { value: '' },
|
||||
selectedBackgroundColor: { value: '' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const checkboxConfig = {
|
|||
],
|
||||
accordian: 'label',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -189,6 +203,7 @@ export const checkboxConfig = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
validation: {
|
||||
mandatory: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -26,6 +26,19 @@ export const colorPickerConfig = {
|
|||
},
|
||||
styles: {
|
||||
visibility: { type: 'toggle', displayName: 'Visibility' },
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
selectedColorHex: '#000000',
|
||||
|
|
@ -45,6 +58,7 @@ export const colorPickerConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ export const dropdownV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -314,6 +326,8 @@ export const dropdownV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select an option' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -78,6 +78,29 @@ export const iconConfig = {
|
|||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'Icon',
|
||||
},
|
||||
boxShadow: {
|
||||
type: 'boxShadow',
|
||||
displayName: 'Box shadow',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: '0px 0px 0px 0px #00000040',
|
||||
},
|
||||
accordian: 'Icon',
|
||||
},
|
||||
},
|
||||
exposedVariables: {},
|
||||
actions: [
|
||||
|
|
@ -116,6 +139,8 @@ export const iconConfig = {
|
|||
styles: {
|
||||
iconColor: { value: '#000' },
|
||||
iconAlign: { value: 'center' },
|
||||
padding: { value: 'default' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000040' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export const modalV2Config = {
|
|||
component: 'ModalV2',
|
||||
defaultSize: {
|
||||
width: 10,
|
||||
height: 34,
|
||||
height: 40,
|
||||
},
|
||||
others: {
|
||||
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
|
||||
|
|
@ -137,7 +137,7 @@ export const modalV2Config = {
|
|||
layout: {
|
||||
top: 24,
|
||||
left: 22,
|
||||
height: 36,
|
||||
height: 40,
|
||||
},
|
||||
displayName: 'ModalFooterCancel',
|
||||
properties: ['text'],
|
||||
|
|
@ -154,7 +154,7 @@ export const modalV2Config = {
|
|||
layout: {
|
||||
top: 24,
|
||||
left: 32,
|
||||
height: 36,
|
||||
height: 40,
|
||||
},
|
||||
displayName: 'ModalFooterConfirm',
|
||||
properties: ['text'],
|
||||
|
|
|
|||
|
|
@ -142,6 +142,18 @@ export const multiselectV2Config = {
|
|||
accordian: 'Options',
|
||||
isFxNotRequired: true,
|
||||
},
|
||||
showClearBtn: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show clear selection button',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
showSearchInput: {
|
||||
type: 'toggle',
|
||||
displayName: 'Show search in options',
|
||||
validation: { schema: { type: 'boolean' }, defaultValue: true },
|
||||
section: 'additionalActions',
|
||||
},
|
||||
loadingState: {
|
||||
type: 'toggle',
|
||||
displayName: 'Loading state',
|
||||
|
|
@ -327,6 +339,8 @@ export const multiselectV2Config = {
|
|||
optionsLoadingState: { value: '{{false}}' },
|
||||
sort: { value: 'asc' },
|
||||
placeholder: { value: 'Select the options' },
|
||||
showClearBtn: { value: '{{true}}' },
|
||||
showSearchInput: { value: '{{true}}' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
loadingState: { value: '{{false}}' },
|
||||
|
|
|
|||
|
|
@ -84,6 +84,19 @@ export const rangeSliderConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: null,
|
||||
|
|
@ -111,6 +124,7 @@ export const rangeSliderConfig = {
|
|||
handleColor: { value: '' },
|
||||
trackColor: { value: '' },
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -89,6 +89,19 @@ export const starratingConfig = {
|
|||
defaultValue: false,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: 0,
|
||||
|
|
@ -112,6 +125,7 @@ export const starratingConfig = {
|
|||
labelColor: { value: '' },
|
||||
visibility: { value: '{{true}}' },
|
||||
disabledState: { value: '{{false}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,19 @@ export const tagsConfig = {
|
|||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
exposedVariables: {},
|
||||
definition: {
|
||||
|
|
@ -54,6 +67,7 @@ export const tagsConfig = {
|
|||
events: [],
|
||||
styles: {
|
||||
visibility: { value: '{{true}}' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ export const toggleSwitchV2Config = {
|
|||
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } },
|
||||
accordian: 'switch',
|
||||
},
|
||||
padding: {
|
||||
type: 'switch',
|
||||
displayName: 'Padding',
|
||||
validation: {
|
||||
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
|
||||
defaultValue: 'default',
|
||||
},
|
||||
isFxNotRequired: true,
|
||||
options: [
|
||||
{ displayName: 'Default', value: 'default' },
|
||||
{ displayName: 'None', value: 'none' },
|
||||
],
|
||||
accordian: 'switch',
|
||||
},
|
||||
},
|
||||
exposedVariables: {
|
||||
value: false,
|
||||
|
|
@ -187,6 +201,7 @@ export const toggleSwitchV2Config = {
|
|||
handleColor: { value: '#FFFFFF' },
|
||||
alignment: { value: 'right' },
|
||||
boxShadow: { value: '0px 0px 0px 0px #00000090' },
|
||||
padding: { value: 'default' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1250,6 +1250,7 @@ $border-radius: 4px;
|
|||
color: var(--slate12) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.data-source-exists {
|
||||
.cm-editor {
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
|
|
@ -1834,6 +1835,7 @@ $border-radius: 4px;
|
|||
|
||||
.cm-scroller {
|
||||
border-bottom-left-radius: 4px;
|
||||
overscroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1853,22 +1855,23 @@ $border-radius: 4px;
|
|||
margin-left: 32px !important;
|
||||
}
|
||||
|
||||
.tjdb-codhinter-wrapper{
|
||||
.codehinter-input{
|
||||
.cm-editor{
|
||||
.tjdb-codhinter-wrapper {
|
||||
.codehinter-input {
|
||||
.cm-editor {
|
||||
// height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
max-height:100px !important;
|
||||
max-height: 100px !important;
|
||||
border-radius: 0 !important;
|
||||
border-right: 0 ;
|
||||
}
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tjdb-limit-offset-codehinter{
|
||||
.cm-editor{
|
||||
|
||||
.tjdb-limit-offset-codehinter {
|
||||
.cm-editor {
|
||||
// height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
max-height:100px !important;
|
||||
max-height: 100px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1896,7 +1899,7 @@ $border-radius: 4px;
|
|||
margin-right: auto;
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
|
|
@ -1917,8 +1920,13 @@ $border-radius: 4px;
|
|||
}
|
||||
|
||||
.restapi-key-value {
|
||||
.code-hinter-wrapper, .code-editor-basic-wrapper, .codehinter-container, .cm-codehinter, .code-editor-query-panel{
|
||||
height:100%;
|
||||
|
||||
.code-hinter-wrapper,
|
||||
.code-editor-basic-wrapper,
|
||||
.codehinter-container,
|
||||
.cm-codehinter,
|
||||
.code-editor-query-panel {
|
||||
height: 100%;
|
||||
max-height: 100px;
|
||||
}
|
||||
}
|
||||
|
|
@ -8295,6 +8295,10 @@ tbody {
|
|||
}
|
||||
}
|
||||
|
||||
.query-manager-btn-shortcut {
|
||||
color: var(--text-disabled) !important;
|
||||
}
|
||||
|
||||
.font-weight-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
|
|||
19
frontend/src/_ui/Icon/solidIcons/Play01.jsx
Normal file
19
frontend/src/_ui/Icon/solidIcons/Play01.jsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
|
||||
const Play01 = ({ fill = '#6A727C', width = '24', className = '', viewBox = '0 0 24 24' }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={width}
|
||||
viewBox={viewBox}
|
||||
className={className}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.5904 14.6487L7.43642 21.5939C5.40287 22.7559 2.87265 21.2875 2.87265 18.9454V5.05514C2.87265 2.71301 5.40287 1.24466 7.43642 2.40669L19.5904 9.35182C21.6397 10.5228 21.6397 13.4777 19.5904 14.6487Z"
|
||||
fill={fill}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Play01;
|
||||
|
|
@ -87,6 +87,7 @@ import Pin from './Pin.jsx';
|
|||
import Unpin from './Unpin.jsx';
|
||||
import AlignRight from './AlignRight';
|
||||
import Play from './Play.jsx';
|
||||
import Play01 from './Play01.jsx';
|
||||
import Plus from './Plus.jsx';
|
||||
import Plus01 from './Plus01.jsx';
|
||||
import Reload from './Reload.jsx';
|
||||
|
|
@ -698,6 +699,8 @@ const Icon = (props) => {
|
|||
return <StudentIcon {...props} />;
|
||||
case 'ai-crown':
|
||||
return <AICrown {...props} />;
|
||||
case 'play01':
|
||||
return <Play01 {...props} />;
|
||||
default:
|
||||
return <Apps {...props} />;
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue