diff --git a/.github/workflows/cypress-platform.yml b/.github/workflows/cypress-platform.yml
index d136991e38..04bde2e492 100644
--- a/.github/workflows/cypress-platform.yml
+++ b/.github/workflows/cypress-platform.yml
@@ -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
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 67a994adf6..b5f3acd0d5 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -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 }}
diff --git a/.github/workflows/render-suspend-labeler.yml b/.github/workflows/render-suspend-labeler.yml
index 7860ae3ade..e9dd2e8b9b 100644
--- a/.github/workflows/render-suspend-labeler.yml
+++ b/.github/workflows/render-suspend-labeler.yml
@@ -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']
})
}
diff --git a/.github/workflows/vulnerability-ci.yml b/.github/workflows/vulnerability-ci.yml
index 15f8425a46..568ab6df31 100644
--- a/.github/workflows/vulnerability-ci.yml
+++ b/.github/workflows/vulnerability-ci.yml
@@ -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:
diff --git a/cypress-tests/cypress/constants/texts/postgreSql.js b/cypress-tests/cypress/constants/texts/postgreSql.js
index 5fdff49ae4..bcc686413d 100644
--- a/cypress-tests/cypress/constants/texts/postgreSql.js
+++ b/cypress-tests/cypress/constants/texts/postgreSql.js
@@ -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",
diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/appTitle.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/appTitle.cy.js
index 34f303769f..994138348a 100644
--- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/appTitle.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/components/appTitle.cy.js
@@ -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}`);
});
});
+
diff --git a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/globalSetingsHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/globalSetingsHappyPath.cy.js
index 195262cfdc..3802f068ed 100644
--- a/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/globalSetingsHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/appbuilder/commonTestcases/globalSetingsHappyPath.cy.js
@@ -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();
});
});
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js
index e0e5db1bd4..6e8dfa0ff0 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js
@@ -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(
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js
index da601cbe30..f221ed3c16 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/clickHouseHappyPath.cy.js
@@ -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,
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js
index 12da4c9684..53fab94f67 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/cosmosDbHappyPath.cy.js
@@ -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(
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js
index 240d8de87b..e16c6d5314 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/couchDbHappyPath.cy.js
@@ -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",
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js
index b8e588cf7d..eb9a030963 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/dynamoDbHappyPath.cy.js
@@ -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(
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js
index 774416c222..c7a1f242fa 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/elasticsearchHappyPath.cy.js
@@ -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",
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js
index 8792279352..6e703fc895 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/fireStoreHappyPath.cy.js
@@ -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
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js
index ee1d515a61..36b39572d4 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/influxDbHappyPath.cy.js
@@ -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",
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js
similarity index 90%
rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.skip.js
rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js
index 3fafe5a23c..4c6c57d596 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.skip.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mariaDbHappyPath.cy.js
@@ -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",
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js
index 41d2a564fb..77d2e2ffa4 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js
@@ -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
);
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js
index 0cf7cb5277..38e221ba0a 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js
@@ -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();
}
});
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js
index 26ca6a8f21..a6ff7595e5 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js
@@ -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(
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js
index 8f04fa4572..267eedea1f 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/rethinkDbHappyPath.cy.skip.js
@@ -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",
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js
index 7a128c1470..12b3817f16 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/s3HappyPath.cy.js
@@ -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
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js
index 90594f8122..4e824aeda5 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/smtpHappyPath.cy.js
@@ -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",
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js
index 22f2afd08e..4409c0577b 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/snowflakeHappyPath.cy.js
@@ -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(
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js
index aa578ed1df..9501fdabbb 100644
--- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js
@@ -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")
);
diff --git a/cypress-tests/cypress/support/utils/button.js b/cypress-tests/cypress/support/utils/button.js
index ddace2f305..66e340965c 100644
--- a/cypress-tests/cypress/support/utils/button.js
+++ b/cypress-tests/cypress/support/utils/button.js
@@ -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);
diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js
index e907a991e2..6f17004409 100644
--- a/cypress-tests/cypress/support/utils/dataSource.js
+++ b/cypress-tests/cypress/support/utils/dataSource.js
@@ -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"),
diff --git a/docker/ce-entrypoint.sh b/docker/ce-entrypoint.sh
new file mode 100755
index 0000000000..4b63af2e45
--- /dev/null
+++ b/docker/ce-entrypoint.sh
@@ -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 "$@"
diff --git a/docker/ce-production.Dockerfile b/docker/ce-production.Dockerfile
index 4e70ecb882..c77ebf128e 100644
--- a/docker/ce-production.Dockerfile
+++ b/docker/ce-production.Dockerfile
@@ -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"]
diff --git a/server/entrypoint.sh b/docker/ee/ee-entrypoint.sh
similarity index 100%
rename from server/entrypoint.sh
rename to docker/ee/ee-entrypoint.sh
diff --git a/docker/ee/ee-production.Dockerfile b/docker/ee/ee-production.Dockerfile
index b69458daa1..e611643f30 100644
--- a/docker/ee/ee-production.Dockerfile
+++ b/docker/ee/ee-production.Dockerfile
@@ -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"]
diff --git a/docker/ee/ee-try-entrypoint-lts.sh b/docker/ee/ee-try-entrypoint-lts.sh
new file mode 100755
index 0000000000..27590534d0
--- /dev/null
+++ b/docker/ee/ee-try-entrypoint-lts.sh
@@ -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
diff --git a/server/try-entrypoint.sh b/docker/ee/ee-try-entrypoint.sh
old mode 100644
new mode 100755
similarity index 96%
rename from server/try-entrypoint.sh
rename to docker/ee/ee-try-entrypoint.sh
index 5843b49ffd..5143e10e75
--- a/server/try-entrypoint.sh
+++ b/docker/ee/ee-try-entrypoint.sh
@@ -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..."
diff --git a/docker/try-tooljet.Dockerfile b/docker/ee/ee-try-tooljet-lts.Dockerfile
similarity index 57%
rename from docker/try-tooljet.Dockerfile
rename to docker/ee/ee-try-tooljet-lts.Dockerfile
index 695f17b913..5eb10b938a 100644
--- a/docker/try-tooljet.Dockerfile
+++ b/docker/ee/ee-try-tooljet-lts.Dockerfile
@@ -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"]
diff --git a/docker/ee/ee-try-tooljet.Dockerfile b/docker/ee/ee-try-tooljet.Dockerfile
new file mode 100644
index 0000000000..11cbe88be3
--- /dev/null
+++ b/docker/ee/ee-try-tooljet.Dockerfile
@@ -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"]
diff --git a/docker/ee/temporal-server.yaml b/docker/ee/temporal-server.yaml
new file mode 100644
index 0000000000..bc17ed934f
--- /dev/null
+++ b/docker/ee/temporal-server.yaml
@@ -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"
diff --git a/docker/ee/temporal-ui-server.yaml b/docker/ee/temporal-ui-server.yaml
new file mode 100644
index 0000000000..4daf530ae2
--- /dev/null
+++ b/docker/ee/temporal-ui-server.yaml
@@ -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
diff --git a/frontend/ee b/frontend/ee
index dcd948d284..a1435b3b0e 160000
--- a/frontend/ee
+++ b/frontend/ee
@@ -1 +1 @@
-Subproject commit dcd948d284b5f14a868480830e09b90496db8572
+Subproject commit a1435b3b0e66a0c731812256d3d495d5bf48d5bb
diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
index 7043c78774..cf40bd2e05 100644
--- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
@@ -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();
diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx
index f6d6419ac4..9507370e13 100644
--- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx
@@ -192,6 +192,7 @@ const RenderWidget = ({
onComponentClick={onComponentClick}
darkMode={darkMode}
componentName={componentName}
+ dataCy={`draggable-widget-${componentName}`}
/>
diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx
index f95baaa328..cbebcb0425 100644
--- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx
+++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx
@@ -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) => {
diff --git a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx
index 6c28bdbb21..c123cb2b9c 100644
--- a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx
+++ b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx
@@ -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}
/>
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
diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx
index 7f8765e287..65e6f2eadd 100644
--- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx
+++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx
@@ -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) => {
)}
@@ -495,9 +517,8 @@ const DynamicEditorBridge = (props) => {
{paramLabel !== 'Type' && isFxNotRequired === undefined && (
{
+ 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,
+ };
+};
diff --git a/frontend/src/AppBuilder/CodeEditor/utils.js b/frontend/src/AppBuilder/CodeEditor/utils.js
index 73d11e6f62..11d6eb3c90 100644
--- a/frontend/src/AppBuilder/CodeEditor/utils.js
+++ b/frontend/src/AppBuilder/CodeEditor/utils.js
@@ -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'
diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx
index 9e6737f41c..244d8e53cf 100644
--- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx
+++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx
@@ -381,7 +381,6 @@ export const BaseQueryManagerBody = ({ darkMode, activeTab, renderCopilot = () =
{activeTab === 1 && renderQueryElement()}
{activeTab === 2 && renderTransformation()}
{activeTab === 3 && renderQueryOptions()}
-
>
)}
diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx
index 013733494f..e7d90fde58 100644
--- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx
+++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx
@@ -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 (
-
-
- {isInDraft && }
+ Run
+ {isMac ? '⌘↩' : 'Ctrl+Enter'}
+
+
);
};
@@ -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 (
-
+
+
+ Preview
+
+
);
};
diff --git a/frontend/src/AppBuilder/QueryManager/Components/Transformation.jsx b/frontend/src/AppBuilder/QueryManager/Components/Transformation.jsx
index 2234fa8e50..e2c2ab56c8 100644
--- a/frontend/src/AppBuilder/QueryManager/Components/Transformation.jsx
+++ b/frontend/src/AppBuilder/QueryManager/Components/Transformation.jsx
@@ -204,7 +204,7 @@ export const Transformation = ({ changeOption, options, darkMode, queryId, rende
-
+
+
{
}, [props.options]);
return (
-
+
+
{
+ 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 {children}
;
+};
+
+export default QueryKeyHooks;
diff --git a/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx b/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx
index ea8623b0c1..61e1f98ed0 100644
--- a/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx
+++ b/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx
@@ -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 && (
-
+
)}
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
index b39924854e..740691b5d3 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
@@ -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,
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js b/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js
index d4ab44b3ab..24690ebfca 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js
@@ -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' },
},
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js b/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js
index ca509979cb..ca4e740885 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js
@@ -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}}' },
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js b/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js
index 6ecdede1a8..6255f81202 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js
@@ -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' },
},
},
};
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/dropdownV2.js b/frontend/src/AppBuilder/WidgetManager/widgets/dropdownV2.js
index cb90554e6b..d7534b25a8 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/dropdownV2.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/dropdownV2.js
@@ -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}}' },
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/icon.js b/frontend/src/AppBuilder/WidgetManager/widgets/icon.js
index 761a2da425..aa22dbb86c 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/icon.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/icon.js
@@ -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' },
},
},
};
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js b/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js
index e7e96c4398..a3c89acf93 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/modalV2.js
@@ -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'],
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/multiselectV2.js b/frontend/src/AppBuilder/WidgetManager/widgets/multiselectV2.js
index 4ab4af57ce..294423abac 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/multiselectV2.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/multiselectV2.js
@@ -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}}' },
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js b/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js
index 320e7a6741..186ae7fbc7 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js
@@ -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' },
},
},
};
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js b/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js
index 8cb239133d..fd2dce59d9 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js
@@ -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' },
},
},
};
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/tags.js b/frontend/src/AppBuilder/WidgetManager/widgets/tags.js
index 73cd44b550..494a34bc7b 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/tags.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/tags.js
@@ -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' },
},
},
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js b/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js
index 4bc21f3a86..4b31bc8f0c 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js
@@ -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' },
},
},
};
diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
index afeb4cf844..a5dbd2a865 100644
--- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx
+++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
@@ -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 && (
- {showFooter && (
+ {!advanced && showFooter && (
{showFilter && (
-
+
)}
>
);
diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/Header/_components/Filter/Filter.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/Header/_components/Filter/Filter.jsx
index 905142fc59..7b1a3cdca6 100644
--- a/frontend/src/AppBuilder/Widgets/NewTable/_components/Header/_components/Filter/Filter.jsx
+++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/Header/_components/Filter/Filter.jsx
@@ -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 }) => {
{localFilters.map((filter, index) => (
{
+ ({ 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}
/>
@@ -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}
/>
@@ -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
;
+};
diff --git a/frontend/src/AppBuilder/_stores/slices/codeHinterSlice.js b/frontend/src/AppBuilder/_stores/slices/codeHinterSlice.js
index 953d253709..854cba49da 100644
--- a/frontend/src/AppBuilder/_stores/slices/codeHinterSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/codeHinterSlice.js
@@ -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;
+ },
});
diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
index 811ebfc958..5ca9429693 100644
--- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
@@ -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);
+ },
},
});
diff --git a/frontend/src/Editor/Components/ColorPicker.jsx b/frontend/src/Editor/Components/ColorPicker.jsx
index 58282490f1..fb5be51f5e 100644
--- a/frontend/src/Editor/Components/ColorPicker.jsx
+++ b/frontend/src/Editor/Components/ColorPicker.jsx
@@ -161,8 +161,8 @@ export const ColorPicker = function ({
: { display: 'none' };
return (
-
-
+
+
setShowColorPicker(true)}
diff --git a/frontend/src/Editor/Components/DropdownV2/CustomMenuList.jsx b/frontend/src/Editor/Components/DropdownV2/CustomMenuList.jsx
index 848de076ae..6be9d65bc7 100644
--- a/frontend/src/Editor/Components/DropdownV2/CustomMenuList.jsx
+++ b/frontend/src/Editor/Components/DropdownV2/CustomMenuList.jsx
@@ -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 (
) : (
{
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) => {
diff --git a/frontend/src/Editor/Components/Link/Link.jsx b/frontend/src/Editor/Components/Link/Link.jsx
index 0b33686278..3a5744df8e 100644
--- a/frontend/src/Editor/Components/Link/Link.jsx
+++ b/frontend/src/Editor/Components/Link/Link.jsx
@@ -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}
>
-
+
{iconVisibility && (
)}
- {linkTextState}
+ {linkTextState}
diff --git a/frontend/src/Editor/Components/Link/link.scss b/frontend/src/Editor/Components/Link/link.scss
index a92f19829f..20b375025f 100644
--- a/frontend/src/Editor/Components/Link/link.scss
+++ b/frontend/src/Editor/Components/Link/link.scss
@@ -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;
+ }
}
+ }
}
diff --git a/frontend/src/Editor/Components/MultiselectV2/CustomOption.jsx b/frontend/src/Editor/Components/MultiselectV2/CustomOption.jsx
index 56b38b2edb..e187cf1292 100644
--- a/frontend/src/Editor/Components/MultiselectV2/CustomOption.jsx
+++ b/frontend/src/Editor/Components/MultiselectV2/CustomOption.jsx
@@ -7,11 +7,23 @@ import { highlightText } from '../DropdownV2/utils';
const CustomOption = (props) => {
return (
-
diff --git a/frontend/src/Editor/Components/MultiselectV2/CustomValueContainer.jsx b/frontend/src/Editor/Components/MultiselectV2/CustomValueContainer.jsx
index 2901abc106..9eb11ea4c6 100644
--- a/frontend/src/Editor/Components/MultiselectV2/CustomValueContainer.jsx
+++ b/frontend/src/Editor/Components/MultiselectV2/CustomValueContainer.jsx
@@ -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;
}
})}
diff --git a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx
index 7d5109edee..c3fd384421 100644
--- a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx
+++ b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx
@@ -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'}
/>
-
+
diff --git a/frontend/src/Editor/WidgetManager/configs/buttonGroup.js b/frontend/src/Editor/WidgetManager/configs/buttonGroup.js
index 65b7e77807..4a1d5ff218 100644
--- a/frontend/src/Editor/WidgetManager/configs/buttonGroup.js
+++ b/frontend/src/Editor/WidgetManager/configs/buttonGroup.js
@@ -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' },
},
},
};
diff --git a/frontend/src/Editor/WidgetManager/configs/checkbox.js b/frontend/src/Editor/WidgetManager/configs/checkbox.js
index c9b6424020..9f991be251 100644
--- a/frontend/src/Editor/WidgetManager/configs/checkbox.js
+++ b/frontend/src/Editor/WidgetManager/configs/checkbox.js
@@ -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}}' },
diff --git a/frontend/src/Editor/WidgetManager/configs/colorPicker.js b/frontend/src/Editor/WidgetManager/configs/colorPicker.js
index b2fddd7e4c..6d93508891 100644
--- a/frontend/src/Editor/WidgetManager/configs/colorPicker.js
+++ b/frontend/src/Editor/WidgetManager/configs/colorPicker.js
@@ -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' },
},
},
};
diff --git a/frontend/src/Editor/WidgetManager/configs/dropdownV2.js b/frontend/src/Editor/WidgetManager/configs/dropdownV2.js
index 308aff1f36..6b011fd082 100644
--- a/frontend/src/Editor/WidgetManager/configs/dropdownV2.js
+++ b/frontend/src/Editor/WidgetManager/configs/dropdownV2.js
@@ -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}}' },
diff --git a/frontend/src/Editor/WidgetManager/configs/icon.js b/frontend/src/Editor/WidgetManager/configs/icon.js
index aea06c976c..40dc8185dd 100644
--- a/frontend/src/Editor/WidgetManager/configs/icon.js
+++ b/frontend/src/Editor/WidgetManager/configs/icon.js
@@ -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' },
},
},
};
diff --git a/frontend/src/Editor/WidgetManager/configs/modalV2.js b/frontend/src/Editor/WidgetManager/configs/modalV2.js
index e7e96c4398..a3c89acf93 100644
--- a/frontend/src/Editor/WidgetManager/configs/modalV2.js
+++ b/frontend/src/Editor/WidgetManager/configs/modalV2.js
@@ -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'],
diff --git a/frontend/src/Editor/WidgetManager/configs/multiselectV2.js b/frontend/src/Editor/WidgetManager/configs/multiselectV2.js
index b603db9c4a..0985f23d08 100644
--- a/frontend/src/Editor/WidgetManager/configs/multiselectV2.js
+++ b/frontend/src/Editor/WidgetManager/configs/multiselectV2.js
@@ -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}}' },
diff --git a/frontend/src/Editor/WidgetManager/configs/rangeslider.js b/frontend/src/Editor/WidgetManager/configs/rangeslider.js
index 151dca3384..541ed95209 100644
--- a/frontend/src/Editor/WidgetManager/configs/rangeslider.js
+++ b/frontend/src/Editor/WidgetManager/configs/rangeslider.js
@@ -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' },
},
},
};
diff --git a/frontend/src/Editor/WidgetManager/configs/starrating.js b/frontend/src/Editor/WidgetManager/configs/starrating.js
index 01240d0369..d6caf8013c 100644
--- a/frontend/src/Editor/WidgetManager/configs/starrating.js
+++ b/frontend/src/Editor/WidgetManager/configs/starrating.js
@@ -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' },
},
},
};
diff --git a/frontend/src/Editor/WidgetManager/configs/tags.js b/frontend/src/Editor/WidgetManager/configs/tags.js
index 8af289b23a..6479eeaad0 100644
--- a/frontend/src/Editor/WidgetManager/configs/tags.js
+++ b/frontend/src/Editor/WidgetManager/configs/tags.js
@@ -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' },
},
},
};
diff --git a/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js b/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js
index 6753fbb50d..6f61a7645d 100644
--- a/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js
+++ b/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js
@@ -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' },
},
},
};
diff --git a/frontend/src/_styles/queryManager.scss b/frontend/src/_styles/queryManager.scss
index 1c63a111e9..1e879bb9c9 100644
--- a/frontend/src/_styles/queryManager.scss
+++ b/frontend/src/_styles/queryManager.scss
@@ -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;
}
}
\ No newline at end of file
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss
index b0bec74b45..ac6a3b6fa6 100644
--- a/frontend/src/_styles/theme.scss
+++ b/frontend/src/_styles/theme.scss
@@ -8295,6 +8295,10 @@ tbody {
}
}
+.query-manager-btn-shortcut {
+ color: var(--text-disabled) !important;
+}
+
.font-weight-500 {
font-weight: 500;
}
diff --git a/frontend/src/_ui/Icon/solidIcons/Play01.jsx b/frontend/src/_ui/Icon/solidIcons/Play01.jsx
new file mode 100644
index 0000000000..42d3c88835
--- /dev/null
+++ b/frontend/src/_ui/Icon/solidIcons/Play01.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+const Play01 = ({ fill = '#6A727C', width = '24', className = '', viewBox = '0 0 24 24' }) => (
+
+);
+
+export default Play01;
diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js
index a64674b0bb..c6eff2e46b 100644
--- a/frontend/src/_ui/Icon/solidIcons/index.js
+++ b/frontend/src/_ui/Icon/solidIcons/index.js
@@ -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
;
case 'ai-crown':
return
;
+ case 'play01':
+ return
;
default:
return
;
}
diff --git a/frontend/src/_ui/Select/SelectComponent.jsx b/frontend/src/_ui/Select/SelectComponent.jsx
index 6675b090e3..2d62352000 100644
--- a/frontend/src/_ui/Select/SelectComponent.jsx
+++ b/frontend/src/_ui/Select/SelectComponent.jsx
@@ -4,6 +4,7 @@ import Select from 'react-select';
import defaultStyles from './styles';
export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSelect, darkMode, ...restProps }) => {
+ const selectRef = React.useRef(null);
const isDarkMode = darkMode ?? localStorage.getItem('darkMode') === 'true';
const {
isMulti = false,
@@ -22,6 +23,7 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
useCustomStyles = false,
isDisabled = false,
borderRadius,
+ openMenuOnFocus = false,
} = restProps;
const customStyles = useCustomStyles ? styles : defaultStyles(isDarkMode, width, height, styles, borderRadius);
@@ -56,6 +58,8 @@ export const SelectComponent = ({ options = [], value, onChange, closeMenuOnSele
return (