merge base

This commit is contained in:
Vijaykant Yadav 2025-05-19 10:53:23 +05:30
commit 3f201e9a28
227 changed files with 5733 additions and 2493 deletions

View file

@ -13,16 +13,18 @@ jobs:
Cypress-App-Builder:
runs-on: ubuntu-22.04
if: |
contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress')
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ce') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ee') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce')
strategy:
matrix:
edition: >-
${{
contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') && fromJson('["ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ee') && fromJson('["ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
fromJson('[]')
}}
@ -158,35 +160,35 @@ jobs:
name: screenshots-appbuilder-${{ matrix.edition }}
path: cypress-tests/cypress/screenshots
Cypress-App-builder-Subpath:
runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-subpath')
# Cypress-App-builder-Subpath:
# runs-on: ubuntu-22.04
# if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
# contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-subpath')
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
# steps:
# - name: Checkout
# uses: actions/checkout@v3
# with:
# ref: ${{ github.event.pull_request.head.ref }}
- name: Create Cypress environment file
id: create-json
uses: jsdaniell/create-json@1.1.2
with:
name: "cypress.env.json"
json: ${{ secrets.CYPRESS_SECRETS }}
dir: "./cypress-tests"
# - name: Create Cypress environment file
# id: create-json
# uses: jsdaniell/create-json@1.1.2
# with:
# name: "cypress.env.json"
# json: ${{ secrets.CYPRESS_SECRETS }}
# dir: "./cypress-tests"
- name: App Builder subpath
uses: cypress-io/github-action@v5
with:
working-directory: ./cypress-tests
config: "baseUrl=http://localhost:80/apps/tooljet/"
config-file: cypress-app-builder.config.js
# - name: App Builder subpath
# uses: cypress-io/github-action@v5
# with:
# working-directory: ./cypress-tests
# config: "baseUrl=http://localhost:80/apps/tooljet/"
# config-file: cypress-app-builder.config.js
- name: Capture Screenshots
uses: actions/upload-artifact@v4
if: always()
with:
name: screenshots
path: cypress-tests/cypress/screenshots
# - name: Capture Screenshots
# uses: actions/upload-artifact@v4
# if: always()
# with:
# name: screenshots
# path: cypress-tests/cypress/screenshots

View file

@ -14,17 +14,19 @@ jobs:
Cypress-Marketplace:
runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace')
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ce') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ee') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce')
strategy:
matrix:
edition: >-
${{
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace') && fromJson('["ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ee') && fromJson('["ee"]') ||
fromJson('[]')
}}
@ -159,13 +161,12 @@ jobs:
"password": "password"
}'
- name: Create Cypress environment file
id: create-json
uses: jsdaniell/create-json@1.1.2
with:
name: "cypress.env.json"
json: ${{ secrets.CYPRESS_SECRETS }}
json: ${{ secrets.CYPRESS_SECRETS_MARKETPLACE }}
dir: "./cypress-tests"
- name: Marketplace
@ -185,8 +186,8 @@ jobs:
Cypress-Marketplace-Subpath:
runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath')
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath')
steps:
- name: Checkout

View file

@ -13,15 +13,18 @@ jobs:
Cypress-Platform:
runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform')
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce')
strategy:
matrix:
edition: >-
${{
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform') && fromJson('["ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') && fromJson('["ee"]') ||
fromJson('[]')
}}

View file

@ -80,7 +80,7 @@ jobs:
},
{
"key": "PG_USER",
"value": "tooljet"
"value": "postgres"
},
{
"key": "PG_PASS",
@ -100,7 +100,7 @@ jobs:
},
{
"key": "TOOLJET_DB_USER",
"value": "tooljet"
"value": "postgres"
},
{
"key": "TOOLJET_DB_PASS",
@ -116,7 +116,7 @@ jobs:
},
{
"key": "PGRST_DB_URI",
"value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ce-tjdb"
"value": "postgres://postgres:postgres@localhost/${{ env.PR_NUMBER }}-ce-tjdb"
},
{
"key": "PGRST_HOST",
@ -168,7 +168,11 @@ jobs:
}
],
"serviceDetails": {
"disk": null,
"disk": {
"name": "tooljet-ce-pr-${{ env.PR_NUMBER }}-postgresql",
"mountPath": "/var/lib/postgresql/13/main",
"sizeGB": 10
},
"env": "docker",
"envSpecificDetails": {
"dockerCommand": "",
@ -279,35 +283,35 @@ jobs:
console.log(e)
}
- name: Install PostgreSQL client
run: |
sudo apt update
sudo apt install postgresql-client -y
# - name: Install PostgreSQL client
# run: |
# sudo apt update
# sudo apt install postgresql-client -y
- name: Wait after installing PostgreSQL
run: sleep 25
# - name: Wait after installing PostgreSQL
# run: sleep 25
- name: Drop PostgreSQL PR databases
env:
PGHOST: ${{ secrets.RENDER_DS_PG_HOST }}
PGPORT: 5432
PGUSER: ${{ secrets.RENDER_DS_PG_USER }}
PGDATABASE: ${{ env.PR_NUMBER }}-ce
PGTJBDATABASE: ${{ env.PR_NUMBER }}-ce-tjdb
run: |
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then
echo "Database $PGDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;"
else
echo "Database $PGDATABASE does not exist."
fi
# - name: Drop PostgreSQL PR databases
# env:
# PGHOST: ${{ secrets.RENDER_DS_PG_HOST }}
# PGPORT: 5432
# PGUSER: ${{ secrets.RENDER_DS_PG_USER }}
# PGDATABASE: ${{ env.PR_NUMBER }}-ce
# PGTJBDATABASE: ${{ env.PR_NUMBER }}-ce-tjdb
# run: |
# if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then
# echo "Database $PGDATABASE exists, deleting..."
# PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;"
# else
# echo "Database $PGDATABASE does not exist."
# fi
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then
echo "Database $PGTJBDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;"
else
echo "Database $PGTJBDATABASE does not exist."
fi
# if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then
# echo "Database $PGTJBDATABASE exists, deleting..."
# PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;"
# else
# echo "Database $PGTJBDATABASE does not exist."
# fi
suspend-ce-review-app:
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ce-review-app' }}
@ -317,7 +321,7 @@ jobs:
- name: Suspend service
run: |
export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--url 'https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id')
@ -349,7 +353,7 @@ jobs:
- name: Resume service
run: |
export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--url 'https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id')
@ -389,6 +393,39 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Sync repo
uses: actions/checkout@v3
- name: Check if Forked Repository
id: check_repo
run: |
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
echo "is_fork=true" >> $GITHUB_ENV
echo "FORKED_OWNER=${{ github.event.pull_request.head.repo.owner.login }}" >> $GITHUB_ENV
else
echo "is_fork=false" >> $GITHUB_ENV
fi
- name: Set Repository URL
run: |
if [[ "$is_fork" == "true" ]]; then
echo "REPO_URL=https://github.com/${FORKED_OWNER}/ToolJet" >> $GITHUB_ENV
else
echo "REPO_URL=https://github.com/ToolJet/ToolJet" >> $GITHUB_ENV
fi
- name: Fetch and Checkout Forked Branch
if: env.is_fork == 'true'
run: |
git fetch origin pull/${{ github.event.number }}/head:${{ env.BRANCH_NAME }}
git checkout ${{ env.BRANCH_NAME }}
- name: Checkout Default Branch
if: env.is_fork == 'false'
uses: actions/checkout@v3
- name: Creating deployment for Enterprise Edition
id: create-ee-deployment
run: |
@ -404,7 +441,7 @@ jobs:
"name": "ToolJet EE PR #${{ env.PR_NUMBER }}",
"notifyOnFail": "default",
"ownerId": "tea-caeo4bj19n072h3dddc0",
"repo": "https://github.com/ToolJet/ToolJet",
"repo": "'"$REPO_URL"'",
"slug": "tooljet-ee-pr-${{ env.PR_NUMBER }}",
"suspended": "not_suspended",
"suspenders": [],
@ -420,7 +457,7 @@ jobs:
},
{
"key": "PG_USER",
"value": "tooljet"
"value": "postgres"
},
{
"key": "PG_PASS",
@ -440,7 +477,7 @@ jobs:
},
{
"key": "TOOLJET_DB_USER",
"value": "tooljet"
"value": "postgres"
},
{
"key": "TOOLJET_DB_PASS",
@ -456,7 +493,7 @@ jobs:
},
{
"key": "PGRST_DB_URI",
"value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ee-tjdb"
"value": "postgres://postgres:postgres@localhost/${{ env.PR_NUMBER }}-ee-tjdb"
},
{
"key": "PGRST_HOST",
@ -536,7 +573,11 @@ jobs:
}
],
"serviceDetails": {
"disk": null,
"disk": {
"name": "tooljet-ee-pr-${{ env.PR_NUMBER }}-postgresql",
"mountPath": "/var/lib/postgresql/13/main",
"sizeGB": 10
},
"env": "docker",
"envSpecificDetails": {
"dockerCommand": "",
@ -549,7 +590,7 @@ jobs:
"port": 80,
"protocol": "TCP"
}],
"plan": "starter",
"plan": "standard",
"pullRequestPreviewsEnabled": "no",
"region": "oregon",
"url": "https://tooljet-ee-pr-${{ env.PR_NUMBER }}.onrender.com"
@ -647,35 +688,35 @@ jobs:
console.log(e)
}
- name: Install PostgreSQL client
run: |
sudo apt update
sudo apt install postgresql-client -y
# - name: Install PostgreSQL client
# run: |
# sudo apt update
# sudo apt install postgresql-client -y
- name: Wait after installing PostgreSQL
run: sleep 25
# - name: Wait after installing PostgreSQL
# run: sleep 25
- name: Drop PostgreSQL PR databases
env:
PGHOST: ${{ secrets.RENDER_DS_PG_HOST }}
PGPORT: 5432
PGUSER: ${{ secrets.RENDER_DS_PG_USER }}
PGDATABASE: ${{ env.PR_NUMBER }}-ee
PGTJBDATABASE: ${{ env.PR_NUMBER }}-ee-tjdb
run: |
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then
echo "Database $PGDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;"
else
echo "Database $PGDATABASE does not exist."
fi
# - name: Drop PostgreSQL PR databases
# env:
# PGHOST: ${{ secrets.RENDER_DS_PG_HOST }}
# PGPORT: 5432
# PGUSER: ${{ secrets.RENDER_DS_PG_USER }}
# PGDATABASE: ${{ env.PR_NUMBER }}-ee
# PGTJBDATABASE: ${{ env.PR_NUMBER }}-ee-tjdb
# run: |
# if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then
# echo "Database $PGDATABASE exists, deleting..."
# PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;"
# else
# echo "Database $PGDATABASE does not exist."
# fi
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then
echo "Database $PGTJBDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;"
else
echo "Database $PGTJBDATABASE does not exist."
fi
# if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then
# echo "Database $PGTJBDATABASE exists, deleting..."
# PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;"
# else
# echo "Database $PGTJBDATABASE does not exist."
# fi
suspend-ee-review-app:
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ee-review-app' }}
@ -685,7 +726,7 @@ jobs:
- name: Suspend service
run: |
export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--url 'https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id')
@ -717,7 +758,7 @@ jobs:
- name: Resume service
run: |
export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--url 'https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id')

View file

@ -1 +1 @@
3.11.0
3.12.1

View file

@ -39,11 +39,11 @@ module.exports = defineConfig({
chromeWebSecurity: false,
trashAssetsBeforeRuns: true,
e2e: {
setupNodeEvents(on, config) {
setupNodeEvents (on, config) {
config.baseUrl = environment.baseUrl;
on("task", {
readPdf(pathToPdf) {
readPdf (pathToPdf) {
return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf);
let dataBuffer = fs.readFileSync(pdfPath);
@ -55,7 +55,7 @@ module.exports = defineConfig({
});
on("task", {
readXlsx(filePath) {
readXlsx (filePath) {
return new Promise((resolve, reject) => {
try {
let dataBuffer = fs.readFileSync(filePath);
@ -69,7 +69,7 @@ module.exports = defineConfig({
});
on("task", {
deleteFolder(folderName) {
deleteFolder (folderName) {
return new Promise((resolve, reject) => {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) {
@ -83,7 +83,7 @@ module.exports = defineConfig({
});
on("task", {
dbConnection({ dbconfig, sql }) {
dbConnection ({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig);
return client.query(sql);
},
@ -97,9 +97,9 @@ module.exports = defineConfig({
baseUrl: environment.baseUrl,
configFile: environment.configFile,
specPattern: [
"cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/!(userFlow)/**/*.cy.js",
"cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug).cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js",
],
numTestsKeptInMemory: 1,

View file

@ -689,7 +689,7 @@ Cypress.Commands.add(
name: dataSourceName,
options: [
{ key: "connection_type", value: "manual", encrypted: false },
{ key: "host", value: "35.238.9.114" },
{ key: "host", value: "9.234.17.31" },
{ key: "port", value: 5432 },
{ key: "database", value: "student" },
{ key: "username", value: "postgres" },

View file

@ -6,6 +6,7 @@ import { passwordInputText } from "Texts/passwordInput";
import { importSelectors } from "Selectors/exportImport";
import { importText } from "Texts/exportImport";
import { onboardingSelectors } from "Selectors/onboarding";
import { selectAppCardOption } from "Support/utils/common";
const API_ENDPOINT =
Cypress.env("environment") === "Community"
@ -160,13 +161,15 @@ Cypress.Commands.add(
Cypress.Commands.add("deleteApp", (appName) => {
cy.intercept("DELETE", "/api/apps/*").as("appDeleted");
cy.get(commonSelectors.appCard(appName))
.realHover()
.find(commonSelectors.appCardOptionsButton)
.realHover()
.click();
cy.get(commonSelectors.deleteAppOption).click();
selectAppCardOption(
appName,
commonSelectors.appCardOptions(commonText.deleteAppOption)
);
cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
cy.wait("@appDeleted");
});
@ -394,39 +397,37 @@ Cypress.Commands.add("getPosition", (componentName) => {
});
Cypress.Commands.add("defaultWorkspaceLogin", () => {
cy.apiLogin();
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `
SELECT id FROM organizations WHERE name = 'My workspace';`,
}).then((resp) => {
const workspaceId = resp.rows[0].id;
// cy.intercept("GET", API_ENDPOINT).as("library_apps");
cy.visit("/my-workspace");
cy.wait(2000)
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
// cy.wait("@library_apps");
cy.apiLogin(
"dev@tooljet.io",
"password",
workspaceId,
"/my-workspace"
).then(() => {
cy.visit("/");
cy.wait(2000);
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
});
});
});
Cypress.Commands.add(
"visitSlug",
({
actualUrl,
errorUrls = [
`${Cypress.config("baseUrl")}/error/unknown`,
`${Cypress.config("baseUrl")}/error/restricted`,
],
}) => {
if (!actualUrl) {
throw new Error("actualUrl is required for visitSlug command.");
Cypress.Commands.add("visitSlug", ({ actualUrl }) => {
cy.visit(actualUrl);
cy.wait(1000);
cy.url().then((currentUrl) => {
if (currentUrl !== actualUrl) {
cy.visit(actualUrl);
cy.wait(1000);
}
cy.visit(actualUrl);
cy.url().then((url) => {
if (errorUrls.includes(url)) {
cy.log(`Navigation resulted in error URL: ${url}. Retrying...`);
cy.visit(actualUrl);
cy.wait(1000);
}
});
}
);
});
});
Cypress.Commands.add("releaseApp", () => {
@ -513,13 +514,58 @@ Cypress.Commands.overwrite(
}
);
Cypress.Commands.add("installMarketplacePlugin", (pluginName) => {
const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`;
cy.visit(MARKETPLACE_URL);
cy.wait(1000);
cy.get('[data-cy="-list-item"]').eq(0).click();
cy.wait(1000);
cy.get("body").then(($body) => {
if ($body.find(".plugins-card").length === 0) {
cy.log("No plugins found, proceeding to install...");
installPlugin(pluginName);
} else {
cy.get(".plugins-card").then(($cards) => {
const isInstalled = $cards.toArray().some((card) => {
return (
Cypress.$(card)
.find(".font-weight-medium.text-capitalize")
.text()
.trim() === pluginName
);
});
if (isInstalled) {
cy.log(`${pluginName} is already installed. Skipping installation.`);
cy.get(commonSelectors.globalDataSourceIcon).click();
} else {
installPlugin(pluginName);
cy.get(commonSelectors.globalDataSourceIcon).click();
}
});
}
});
function installPlugin (pluginName) {
cy.get('[data-cy="-list-item"]').eq(1).click();
cy.wait(1000);
cy.contains(".plugins-card", pluginName).within(() => {
cy.get(".marketplace-install").click();
cy.wait(1000);
});
}
});
Cypress.Commands.add("verifyElement", (selector, text, eqValue) => {
const element =
eqValue !== undefined ? cy.get(selector).eq(eqValue) : cy.get(selector);
element.should("be.visible").and("have.text", text);
});
Cypress.Commands.add("getAppId", (appName) => {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
@ -529,3 +575,33 @@ Cypress.Commands.add("getAppId", (appName) => {
return appId;
});
});
Cypress.Commands.add("uninstallMarketplacePlugin", (pluginName) => {
const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`;
cy.visit(MARKETPLACE_URL);
cy.wait(1000);
cy.get('[data-cy="-list-item"]').eq(0).click();
cy.wait(1000);
cy.get(".plugins-card").each(($card) => {
cy.wrap($card)
.find(".font-weight-medium.text-capitalize")
.invoke("text")
.then((text) => {
if (text.trim() === pluginName) {
cy.wrap($card).find(".link-primary").contains("Remove").click();
cy.wait(1000);
cy.get('[data-cy="delete-plugin-title"]').should("be.visible");
cy.get('[data-cy="yes-button"]').click();
cy.wait(2000);
cy.log(`${pluginName} has been successfully uninstalled.`);
} else {
cy.log(`${pluginName} is not installed. Skipping uninstallation.`);
}
});
});
});

View file

@ -6,7 +6,8 @@ export const commonSelectors = {
toastMessage: ".go3958317564",
oldToastMessage: ".go318386747",
appSlugAccept: '[data-cy="app-slug-accepted-label"]',
newToastMessage: '.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551 > .go3958317564',
newToastMessage:
'.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551 > .go3958317564',
toastCloseButton: '[data-cy="toast-close-button"]',
editButton: "[data-cy=edit-button]",
workspaceConstantNameInput: '[data-cy="name-input-field"]',
@ -18,7 +19,7 @@ export const commonSelectors = {
appCardOptionsButton: "[data-cy=app-card-menu-icon]",
autoSave: "[data-cy=autosave-indicator]",
nameInputFieldd: "[data-cy=name-input-field]",
valueInputFieldd: '[data-cy=value-input-field]',
valueInputFieldd: "[data-cy=value-input-field]",
skipButton: ".driver-close-btn",
skipInstallationModal: "[data-cy=skip-button]",
homePageLogo: "[data-cy=home-page-logo]",
@ -395,7 +396,7 @@ export const commonWidgetSelector = {
modalCloseButton: '[data-cy="modal-close-button"]',
iframeLinkLabel: '[data-cy="iframe-link-label"]',
ifameLinkCopyButton: '[data-cy="iframe-link-copy-button"]',
appSlugLabel: '[data-cy="input-field-label"]',
appSlugLabel: '[data-cy="unique-app-slug-field-label"]',
appSlugInput: '[data-cy="app-slug-input-field"]',
appSlugInfoLabel: '[data-cy="helper-text"]',
appLinkLabel: '[data-cy="app-link-label"]',

View file

@ -24,7 +24,7 @@ export const restAPISelector = {
return `[data-cy="${cyParamName(header)}-delete-button-${cyParamName(index)}"]`;
},
addMoreButton: (header) => {
return `[data-cy="${cyParamName(header)}-add-more-button"]`;
return `[data-cy="${cyParamName(header)}-add-button"]`;
},
dropdownLabel: (label) => {
return `[data-cy="${cyParamName(label)}-dropdown-label"]`;

View file

@ -4,8 +4,8 @@ export const postgreSqlText = {
allDataSources: () => {
return Cypress.env("marketplace_action")
? "All data sources (44)"
: "All data sources (45)";
? "All data sources (45)"
: "All data sources (43)";
},
commonlyUsed: "Commonly used (5)",
allDatabase: () => {

View file

@ -8,11 +8,7 @@ export const editVersionText = {
export const deleteVersionText = {
deleteModalText: (text) => {
// return `Are you sure you want to delete this version - ${cyParamName(
// text
// )}?`;
return `Deleting a version will permanently remove it from all environments.Are you sure you want to delete this version - ${cyParamName(
return `Are you sure you want to delete this version - ${cyParamName(
text
)}?`;
},

View file

@ -32,7 +32,7 @@ import {
addSupportCSAData,
} from "Support/utils/events";
describe("Editor- Test Button widget", () => {
describe("Editor- Test Button widget ", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-button-App`);

View file

@ -66,7 +66,7 @@ describe("Add all Data sources to app", () => {
cy.apiLogin();
});
it("Should verify global data source page", () => {
it.skip("Should verify global data source page", () => {
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.visit(`${data.workspaceSlug}`);
@ -87,7 +87,7 @@ describe("Add all Data sources to app", () => {
);
});
it("Should add all data sources in data source page", () => {
it.skip("Should add all data sources in data source page", () => {
cy.visit(`${data.workspaceSlug}`);
dataSources.forEach((dsName) => {
@ -109,7 +109,7 @@ describe("Add all Data sources to app", () => {
});
});
it("Should add all data sources in the app", () => {
it.skip("Should add all data sources in the app", () => {
cy.visit(`${data.workspaceSlug}`);
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
@ -135,7 +135,7 @@ describe("Add all Data sources to app", () => {
});
});
it("Should install all makretplace plugins and add them into the app", () => {
it.skip("Should install all makretplace plugins and add them into the app", () => {
cy.visit(`${data.workspaceSlug}`);
const dataSourcesMarketplace = [
"Plivo",
@ -189,12 +189,15 @@ describe("Add all Data sources to app", () => {
cy.wrap(dataSourcesMarketplace).each((dsName) => {
cy.get(commonSelectors.globalDataSourceIcon).click();
selectAndAddDataSource("databases", dsName, dsName);
cy.wait(500);
cy.wait(1000);
});
cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.appNameInput).click().type(data.dsNamefake1);
cy.get(commonSelectors.dashboardIcon).should("be.visible").click();
cy.get(commonSelectors.appCreateButton).should("be.visible").click();
cy.get(commonSelectors.appNameInput)
.should("be.visible")
.click()
.type(data.dsNamefake1);
cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough();
@ -203,7 +206,7 @@ describe("Add all Data sources to app", () => {
cy.get(".css-4e90k9").type(
`cypress-${cyParamName(dsName)}-${cyParamName(dsName)}`
);
cy.wait(500);
cy.wait(1000);
cy.contains(
`[id*="react-select-"]`,
@ -212,7 +215,7 @@ describe("Add all Data sources to app", () => {
.should("be.visible")
.click();
cy.wait(500);
cy.wait(1000);
});
});
});

View file

@ -19,13 +19,14 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.queryName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Airtable", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection AirTable form", () => {
@ -199,7 +200,7 @@ describe("Data source Airtable", () => {
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName1);
cy.get('[data-cy="query-rename-input"]').clear().type(data.queryName);
cy.get(airTableSelector.operationSelectDropdown)
.click()
@ -225,7 +226,7 @@ describe("Data source Airtable", () => {
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName1}) completed.`
`Query (${data.queryName}) completed.`
);
// Verify Delete record operation
@ -277,7 +278,7 @@ describe("Data source Airtable", () => {
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`Query (${data.dsName1}) completed.`
`Query (${data.queryName}) completed.`
);
deleteAppandDatasourceAfterExecution(
data.dsName,

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source amazon athena", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on amazon athena connection form", () => {
it.skip("Should verify elements on amazon athena connection form", () => {
const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName");
@ -97,7 +97,7 @@ describe("Data source amazon athena", () => {
deleteDatasource(`cypress-${data.dsName}-Amazon-Athena`);
});
it("Should verify the functionality of amazon athena connection form.", () => {
it.skip("Should verify the functionality of amazon athena connection form.", () => {
const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName");
@ -134,7 +134,7 @@ describe("Data source amazon athena", () => {
deleteDatasource(`cypress-${data.dsName}-amazon-Athena`);
});
it("Should able to run the query with valid conection", () => {
it.skip("Should able to run the query with valid conection", () => {
const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName");
@ -188,11 +188,13 @@ describe("Data source amazon athena", () => {
cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
cy.get(`[data-cy="list-query-${data.dsName}"]`).should("be.visible");
cy.get('[data-cy="query-input-field"]').clearAndTypeOnCodeMirror(
"SHOW DATABASES;"
);
cy.get(
'[data-cy="query-input-field"] >>> .cm-editor >> .cm-content > .cm-line'
).should("have.text", "SHOW DATABASES;");
cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source amazon ses", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on amazonses connection form", () => {
it.skip("Should verify elements on amazonses connection form", () => {
const Accesskey = Cypress.env("amazonSes_accessKey");
const Secretkey = Cypress.env("amazonSes_secretKey");
@ -80,7 +80,7 @@ describe("Data source amazon ses", () => {
deleteDatasource(`cypress-${data.dsName}-Amazon-ses`);
});
it("Should verify the functionality of amazonses connection form.", () => {
it.skip("Should verify the functionality of amazonses connection form.", () => {
selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName);
cy.get(".react-select__dropdown-indicator").eq(1).click();
@ -112,7 +112,7 @@ describe("Data source amazon ses", () => {
deleteDatasource(`cypress-${data.dsName}-amazon-ses`);
});
it("Should able to run the query with valid conection", () => {
it.skip("Should able to run the query with valid conection", () => {
const email = "adish" + "@" + "tooljet.com";
selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName);

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source AppWrite", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on appwrite connection form", () => {
it.skip("Should verify elements on appwrite connection form", () => {
const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID");
@ -100,7 +100,7 @@ describe("Data source AppWrite", () => {
deleteDatasource(`cypress-${data.dsName}-Appwrite`);
});
it("Should verify the functionality of appwrite connection form.", () => {
it.skip("Should verify the functionality of appwrite connection form.", () => {
const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID");
@ -150,7 +150,7 @@ describe("Data source AppWrite", () => {
deleteDatasource(`cypress-${data.dsName}-Appwrite`);
});
it("Should be able to run the query with a valid connection", () => {
it.skip("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID");

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source AWS Lambda", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on AWS Lambda connection form", () => {
it.skip("Should verify elements on AWS Lambda connection form", () => {
const Accesskey = Cypress.env("awslamda_access");
const Secretkey = Cypress.env("awslamda_secret");
@ -80,9 +80,10 @@ describe("Data source AWS Lambda", () => {
);
deleteDatasource(`cypress-${data.dsName}-aws-lambda`);
cy.uninstallMarketplacePlugin("AWS Lambda");
});
it("Should verify the functionality of AWS Lambda connection form", () => {
it.skip("Should verify the functionality of AWS Lambda connection form", () => {
const Accesskey = Cypress.env("awslamda_access");
const Secretkey = Cypress.env("awslamda_secret");
@ -113,9 +114,10 @@ describe("Data source AWS Lambda", () => {
);
deleteDatasource(`cypress-${data.dsName}-aws-lambda`);
cy.uninstallMarketplacePlugin("AWS Lambda");
});
it("Should able to run the query with valid conection", () => {
it.skip("Should able to run the query with valid conection", () => {
const Accesskey = Cypress.env("awslamda_access");
const Secretkey = Cypress.env("awslamda_secret");

View file

@ -21,15 +21,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source AWS Textract", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on AWS Textract connection form", () => {
it.skip("Should verify elements on AWS Textract connection form", () => {
const Accesskey = Cypress.env("awstextract_access");
const Secretkey = Cypress.env("awstextract_secret");
@ -87,7 +87,7 @@ describe("Data source AWS Textract", () => {
deleteDatasource(`cypress-${data.dsName}-aws-textract`);
});
it("Should verify functionality of AWS Textract connection form", () => {
it.skip("Should verify functionality of AWS Textract connection form", () => {
const Accesskey = Cypress.env("awstextract_access");
const Secretkey = Cypress.env("awstextract_secret");
@ -122,9 +122,10 @@ describe("Data source AWS Textract", () => {
);
deleteDatasource(`cypress-${data.dsName}-aws-textract`);
cy.uninstallMarketplacePlugin("AWS Textract");
});
it("Should able to run the query with valid conection", () => {
it.skip("Should able to run the query with valid conection", () => {
const Accesskey = Cypress.env("awstextract_access");
const Secretkey = Cypress.env("awstextract_secret");

View file

@ -17,8 +17,8 @@ data.customText = fake.randomSentence;
describe("Data source Azure Blob Storage", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source baserow", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on baserow connection form", () => {
it.skip("Should verify elements on baserow connection form", () => {
const Apikey = Cypress.env("baserow_apikey");
cy.get(commonSelectors.globalDataSourceIcon).click();
@ -78,7 +78,7 @@ describe("Data source baserow", () => {
deleteDatasource(`cypress-${data.dsName}-baserow`);
});
it("Should verify the functionality of baserow connection form.", () => {
it.skip("Should verify the functionality of baserow connection form.", () => {
const Apikey = Cypress.env("baserow_apikey");
selectAndAddDataSource("databases", baseRowText.baserow, data.dsName);
@ -103,7 +103,7 @@ describe("Data source baserow", () => {
deleteDatasource(`cypress-${data.dsName}-baserow`);
});
it("Should be able to run the query with a valid connection", () => {
it.skip("Should be able to run the query with a valid connection", () => {
const baserowTableID = Cypress.env("baserow_tableid");
const baserowRowID = Cypress.env("baserow_rowid");
const Apikey = Cypress.env("baserow_apikey");

View file

@ -16,8 +16,8 @@ const data = {};
describe("Data source BigQuery", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -21,8 +21,8 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data source DynamoDB", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -17,9 +17,12 @@ import {
const data = {};
describe("Data source Elasticsearch", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on Elasticsearch connection form", () => {
@ -123,14 +126,14 @@ describe("Data source Elasticsearch", () => {
"have.text",
elasticsearchText.errorConnectionRefused
);
deleteDatasource(`cypress-${data.lastName}-elasticsearch`);
deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`);
});
it("Should verify the functionality of Elasticsearch connection form.", () => {
selectAndAddDataSource(
"databases",
elasticsearchText.elasticSearch,
data.lastName
data.dataSourceName
);
fillDataSourceTextField(
@ -210,12 +213,12 @@ describe("Data source Elasticsearch", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-elasticsearch-button"]`
`[data-cy="cypress-${data.dataSourceName}-elasticsearch-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.lastName}-elasticsearch`
`cypress-${data.dataSourceName}-elasticsearch`
);
deleteDatasource(`cypress-${data.lastName}-elasticsearch`);
deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`);
});
});

View file

@ -17,8 +17,8 @@ const data = {};
describe("Data source Firestore", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -19,12 +19,12 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source GraphQL", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on GraphQL connection form", () => {

View file

@ -20,13 +20,13 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source HarperDB", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on HarperDB connection form", () => {
@ -102,6 +102,7 @@ describe("Data source HarperDB", () => {
);
deleteDatasource(`cypress-${data.dsName}-HarperDB`);
cy.uninstallMarketplacePlugin("HarperDB");
});
it("Should verify functionality of HarperDB connection form", () => {
@ -156,9 +157,10 @@ describe("Data source HarperDB", () => {
);
deleteDatasource(`cypress-${data.dsName}-HarperDB`);
cy.uninstallMarketplacePlugin("HarperDB");
});
it("Should be able to run the query with a valid connection", () => {
it.skip("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("harperdb_host");
const Port = Cypress.env("harperdb_port");
const Username = Cypress.env("harperdb_username");

View file

@ -23,8 +23,8 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -20,12 +20,12 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source minio", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on minio connection form", () => {
@ -157,7 +157,7 @@ describe("Data source minio", () => {
deleteDatasource(`cypress-${data.dsName}-minio`);
});
it("Should be able to run the query with a valid connection", () => {
it.skip("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("minio_host");
const Port = Cypress.env("minio_port");
const AccessKey = Cypress.env("minio_accesskey");

View file

@ -27,8 +27,8 @@ const data = {};
describe("Data source MongoDB", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
@ -133,7 +133,7 @@ describe("Data source MongoDB", () => {
"have.text",
mongoDbText.errorConnectionRefused
);
cy.get('[data-cy="query-select-dropdown"]').type(
cy.get('[data-cy="connection-type-select-dropdown"]').type(
mongoDbText.optionConnectUsingConnectionString
);
cy.get('[data-cy="label-connection-string"]').verifyVisibleElement(

View file

@ -26,8 +26,8 @@ const data = {};
describe("Data sources MySql", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -15,7 +15,7 @@ import {
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.apiLogin();
// cy.createApp();
});

View file

@ -20,14 +20,14 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
it.skip("Should verify elements on connection form", () => {
cy.log(process.env.NODE_ENV);
cy.log(postgreSqlText.allDatabase());
cy.get(commonSelectors.globalDataSourceIcon).click();
@ -140,7 +140,7 @@ describe("Data sources", () => {
deleteDatasource(`cypress-${data.dataSourceName}-postgresql`);
});
it("Should verify the functionality of PostgreSQL connection form.", () => {
it.skip("Should verify the functionality of PostgreSQL connection form.", () => {
selectAndAddDataSource(
"databases",
postgreSqlText.postgreSQL,

View file

@ -23,7 +23,7 @@ data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Redis", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
});
it("Should verify elements on connection Redis form", () => {
@ -215,7 +215,7 @@ describe("Data source Redis", () => {
deleteDatasource(`cypress-${data.dsName}-redis`);
});
it("Should able to run the query with valid conection", () => {
it.skip("Should able to run the query with valid conection", () => {
selectAndAddDataSource("databases", redisText.redis, data.dsName);
fillDataSourceTextField(

View file

@ -20,7 +20,7 @@ const clientAuthenticationDropdown =
describe("Data source Rest API", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
@ -332,7 +332,6 @@ describe("Data source Rest API", () => {
deleteDatasource(`cypress-${data.dataSourceName}-restapi`);
});
it("Should verify basic connection for Rest API", () => {
const storedId = Cypress.env("storedId");
cy.apiCreateGDS(
`${Cypress.env("server_host")}/api/data-sources`,
`cypress-${data.dataSourceName}-restapi`,

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -20,7 +20,7 @@ const data = {};
describe("Data sources AWS S3", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -15,7 +15,7 @@ import {
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.apiLogin();
// cy.createApp();
});

View file

@ -13,8 +13,8 @@ const data = {};
describe("Data source SMTP", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -20,8 +20,8 @@ import {
const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -21,8 +21,8 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit("/");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -21,15 +21,15 @@ import { dataSourceSelector } from "../../../../../constants/selectors/dataSourc
import { pluginSelectors } from "Selectors/plugins";
const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Twilio", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on Twilio connection form", () => {
it.skip("Should verify elements on Twilio connection form", () => {
const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID");
@ -89,7 +89,7 @@ describe("Data source Twilio", () => {
deleteDatasource(`cypress-${data.dsName}-twilio`);
});
it("Should verify functionality of Twilio connection form", () => {
it.skip("Should verify functionality of Twilio connection form", () => {
const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID");
@ -128,7 +128,7 @@ describe("Data source Twilio", () => {
deleteDatasource(`cypress-${data.dsName}-twilio`);
});
it("Should be able to run the query with a valid connection", () => {
it.skip("Should be able to run the query with a valid connection", () => {
const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID");

View file

@ -20,7 +20,7 @@ const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
cy.apiLogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -46,9 +46,7 @@ describe("App Export", () => {
});
it("Verify the elements of export dialog box", () => {
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("walkthroughCompleted", "true");
});
cy.skipWalkthrough()
cy.apiLogin();
cy.visit(`${data.workspaceSlug}`);

View file

@ -34,6 +34,7 @@ describe("App Import Functionality", () => {
cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.apiLogout();
cy.skipWalkthrough()
});
it("should verify app import functionality", () => {
@ -100,12 +101,13 @@ describe("App Import Functionality", () => {
.and("have.text", importText.appImportedToastMessage);
// Verify imported app
cy.get(".driver-close-btn").click();
cy.get(commonSelectors.toastCloseButton).click();
cy.wait(500);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
"three-versions"
);
cy.get(appVersionSelectors.currentVersionField("v3")).should("be.visible");
// Configure app
cy.skipEditorPopover();

View file

@ -27,17 +27,21 @@ describe("App Slug", () => {
});
it("Verify app slug cases in global settings", () => {
cy.apiLogin();
const workspaceId = Cypress.env("workspaceId");
const appId = Cypress.env("appId");
const appUrl = `${host}/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`;
cy.visit("/my-workspace");
cy.wait(1000);
cy.apiLogin();
cy.skipWalkthrough();
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("walkthroughCompleted", "true");
cy.visit(appUrl);
cy.url().then((url) => {
if (url !== appUrl) {
cy.visit(appUrl);
}
});
cy.visit(`/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`);
cy.url().should("eq", appUrl);
cy.wait(1000);
cy.get(commonSelectors.leftSideBarSettingsButton).click();

View file

@ -78,11 +78,11 @@ describe("Private and Public apps", {
// Test private access
logout();
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.wait(2000);
cy.appUILogin();
@ -116,6 +116,9 @@ describe("Private and Public apps", {
inviteUserToWorkspace(data.firstName, data.email);
logout();
cy.visit("/");
cy.wait(2000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
// Test private access
cy.visitSlug({
@ -141,6 +144,8 @@ describe("Private and Public apps", {
cy.wait(1000);
cy.apiMakeAppPublic();
logout();
cy.wait(1000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
@ -177,6 +182,9 @@ describe("Private and Public apps", {
cy.apiMakeAppPublic();
logout();
cy.wait(1000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
@ -229,6 +237,8 @@ describe("Private and Public apps", {
cy.get('[data-cy="viewer-page-logo"]').click();
logout();
cy.wait(1000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
// Setup new workspace and app
cy.defaultWorkspaceLogin();

View file

@ -123,7 +123,7 @@ describe("App Version", () => {
releasedVersionAndVerify("v2");
});
it.only("should verify version management with components and queries", () => {
it("should verify version management with components and queries", () => {
// Initial setup with component and datasource
cy.apiAddComponentToApp(
data.appName,

View file

@ -80,8 +80,8 @@ describe("Workspace constants", () => {
addNewconstants("restapiHeaderKey", "customHeader");
addNewconstants("restapiHeaderValue", "key=value");
addNewconstants("deleteConst", "deleteconst");
addNewconstants("gconst", "236");
addNewconstants("gconstUrl", "http://34.66.166.236:4000/");
addNewconstants("gconst", "108");
addNewconstants("gconstUrl", "http://20.29.40.108:4000/");
addNewconstants("gconstEndpoint", "production");
// create secret constants
@ -118,6 +118,7 @@ describe("Workspace constants", () => {
//Verify all static and datasource queries output in components
for (let i = 3; i <= 16; i++) {
cy.log("Verifying textinput" + i);
cy.get(commonWidgetSelector.draggableWidget(`textinput${i}`))
.verifyVisibleElement("have.value", "Production environment testing");
}

View file

@ -44,6 +44,164 @@ describe("dashboard", () => {
cy.visit(`${data.workspaceSlug}`);
});
// it("Should verify app card elements and app card operations", () => {
// const customLayout = {
// desktop: { top: 100, left: 20 },
// mobile: { width: 8, height: 50 },
// };
// cy.apiCreateApp(data.appName);
// cy.visit(`${data.workspaceSlug}`);
// cy.wait(2000);
// cy.get(commonSelectors.appCreationDetails).should("be.visible");
// cy.get(commonSelectors.appCard(data.appName)).should("be.visible");
// cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement(
// "have.text",
// data.appName
// );
// viewAppCardOptions(data.appName);
// cy.get(
// commonSelectors.appCardOptions(commonText.changeIconOption)
// ).verifyVisibleElement("have.text", commonText.changeIconOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.addToFolderOption)
// ).verifyVisibleElement("have.text", commonText.addToFolderOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.cloneAppOption)
// ).verifyVisibleElement("have.text", commonText.cloneAppOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.exportAppOption)
// ).verifyVisibleElement("have.text", commonText.exportAppOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.deleteAppOption)
// ).verifyVisibleElement("have.text", commonText.deleteAppOption);
// modifyAndVerifyAppCardIcon(data.appName);
// createFolder(data.folderName);
// viewAppCardOptions(data.appName);
// cy.get(
// commonSelectors.appCardOptions(commonText.addToFolderOption)
// ).click();
// verifyModal(
// dashboardText.addToFolderTitle,
// dashboardText.addToFolderButton,
// dashboardSelector.selectFolder
// );
// cy.get(dashboardSelector.moveAppText).verifyVisibleElement(
// "have.text",
// dashboardText.moveAppText(data.appName)
// );
// cy.get(dashboardSelector.selectFolder).click();
// cy.get(commonSelectors.folderList).contains(data.folderName).click();
// cy.get(dashboardSelector.addToFolderButton).click();
// cy.verifyToastMessage(
// commonSelectors.toastMessage,
// commonText.AddedToFolderToast,
// false
// );
// cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement(
// "have.text",
// dashboardText.folderName(`${data.folderName} (1)`)
// );
// cy.get(dashboardSelector.folderName(data.folderName)).click();
// cy.get(commonSelectors.appCard(data.appName))
// .contains(data.appName)
// .should("be.visible");
// viewAppCardOptions(data.appName);
// cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption))
// .verifyVisibleElement("have.text", commonText.removeFromFolderOption)
// .click();
// verifyConfirmationModal(commonText.appRemovedFromFolderMessage);
// cancelModal(commonText.cancelButton);
// viewAppCardOptions(data.appName);
// cy.get(
// commonSelectors.appCardOptions(commonText.removeFromFolderOption)
// ).click();
// cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
// cy.verifyToastMessage(
// commonSelectors.toastMessage,
// commonText.appRemovedFromFolderTaost,
// false
// );
// cy.get(commonSelectors.modalComponent).should("not.exist");
// cy.get(commonSelectors.empytyFolderImage).should("be.visible");
// cy.get(commonSelectors.emptyFolderText).verifyVisibleElement(
// "have.text",
// commonText.emptyFolderText
// );
// cy.get(commonSelectors.allApplicationsLink).click();
// deleteFolder(data.folderName);
// cy.get(commonSelectors.allApplicationsLink).click();
// cy.wait(1000);
// viewAppCardOptions(data.appName);
// cy.wait(2000);
// cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click();
// cy.get(commonSelectors.exportAllButton).click();
// cy.exec("ls ./cypress/downloads/").then((result) => {
// const downloadedAppExportFileName = result.stdout.split("\n")[0];
// expect(downloadedAppExportFileName).to.contain.string("app");
// });
// viewAppCardOptions(data.appName);
// cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click();
// cy.get('[data-cy="clone-app"]').click();
// cy.get(".go3958317564")
// .should("be.visible")
// .and("have.text", dashboardText.appClonedToast);
// cy.wait(3000);
// cy.renameApp(data.cloneAppName);
// cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25);
// cy.backToApps();
// cy.wait("@appLibrary");
// cy.wait(1000);
// cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
// cy.wait(1000);
// viewAppCardOptions(data.cloneAppName);
// cy.get(commonSelectors.deleteAppOption).click();
// cy.get(commonSelectors.modalMessage).verifyVisibleElement(
// "have.text",
// commonText.deleteAppModalMessage(data.cloneAppName)
// );
// cy.get(
// commonSelectors.buttonSelector(commonText.cancelButton)
// ).verifyVisibleElement("have.text", commonText.cancelButton);
// cy.get(
// commonSelectors.buttonSelector(commonText.modalYesButton)
// ).verifyVisibleElement("have.text", commonText.modalYesButton);
// cancelModal(commonText.cancelButton);
// viewAppCardOptions(data.cloneAppName);
// cy.get(commonSelectors.deleteAppOption).click();
// cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
// cy.verifyToastMessage(
// commonSelectors.toastMessage,
// commonText.appDeletedToast,
// false
// );
// verifyAppDelete(data.cloneAppName);
// cy.wait("@appLibrary");
// cy.deleteApp(data.appName);
// verifyAppDelete(data.appName);
// });
it("should verify the elements on empty dashboard", () => {
cy.intercept("GET", "/api/metadata", {
body: {
@ -171,181 +329,6 @@ describe("dashboard", () => {
verifyTooltip(dashboardSelector.modeToggle, "Mode");
});
it.skip("Should verify app card elements and app card operations", () => {
const customLayout = {
desktop: { top: 100, left: 20 },
mobile: { width: 8, height: 50 },
};
cy.apiCreateApp(data.appName);
cy.openApp();
cy.apiAddComponentToApp(data.appName, "text1", customLayout);
cy.backToApps();
cy.wait(500);
cy.get(commonSelectors.appCard(data.appName))
.parent()
.within(() => {
cy.get(commonSelectors.appCard(data.appName)).should("be.visible");
cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement(
"have.text",
data.appName
);
cy.get(commonSelectors.appCreationDetails).should("be.visible");
//Add the edited details
});
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.changeIconOption)
).verifyVisibleElement("have.text", commonText.changeIconOption);
cy.get(
commonSelectors.appCardOptions(commonText.addToFolderOption)
).verifyVisibleElement("have.text", commonText.addToFolderOption);
cy.get(
commonSelectors.appCardOptions(commonText.cloneAppOption)
).verifyVisibleElement("have.text", commonText.cloneAppOption);
cy.get(
commonSelectors.appCardOptions(commonText.exportAppOption)
).verifyVisibleElement("have.text", commonText.exportAppOption);
cy.get(
commonSelectors.appCardOptions(commonText.deleteAppOption)
).verifyVisibleElement("have.text", commonText.deleteAppOption);
modifyAndVerifyAppCardIcon(data.appName);
createFolder(data.folderName);
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.addToFolderOption)
).click();
verifyModal(
dashboardText.addToFolderTitle,
dashboardText.addToFolderButton,
dashboardSelector.selectFolder
);
cy.get(dashboardSelector.moveAppText).verifyVisibleElement(
"have.text",
dashboardText.moveAppText(data.appName)
);
cy.get(dashboardSelector.selectFolder).click();
cy.get(commonSelectors.folderList).contains(data.folderName).click();
cy.get(dashboardSelector.addToFolderButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.AddedToFolderToast
);
cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement(
"have.text",
dashboardText.folderName(`${data.folderName} (1)`)
);
cy.get(dashboardSelector.folderName(data.folderName)).click();
cy.get(commonSelectors.appCard(data.appName))
.contains(data.appName)
.should("be.visible");
cy.wait(2000);
viewAppCardOptions(data.appName);
cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption))
.verifyVisibleElement("have.text", commonText.removeFromFolderOption)
.click();
verifyConfirmationModal(commonText.appRemovedFromFolderMessage);
cancelModal(commonText.cancelButton);
cy.wait(3000);
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.removeFromFolderOption)
).click();
cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appRemovedFromFolderTaost
);
cy.get(commonSelectors.modalComponent).should("not.exist");
cy.get(commonSelectors.empytyFolderImage).should("be.visible");
cy.get(commonSelectors.emptyFolderText).verifyVisibleElement(
"have.text",
commonText.emptyFolderText
);
cy.get(commonSelectors.allApplicationsLink).click();
deleteFolder(data.folderName);
cy.get(commonSelectors.allApplicationsLink).click();
cy.wait(3000);
viewAppCardOptions(data.appName);
cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click();
cy.get('[data-cy="clone-app"]').click();
cy.get(".go3958317564")
.should("be.visible")
.and("have.text", dashboardText.appClonedToast);
cy.wait(3000);
cy.renameApp(data.cloneAppName);
cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25);
cy.backToApps();
cy.wait("@appLibrary");
cy.wait(1000);
cy.reloadAppForTheElement(data.cloneAppName);
cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(commonSelectors.dashboardIcon).click();
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click();
cy.get(commonSelectors.exportAllButton).click();
cy.exec("ls ./cypress/downloads/").then((result) => {
const downloadedAppExportFileName = result.stdout.split("\n")[0];
expect(downloadedAppExportFileName).to.contain.string("app");
});
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.deleteAppOption).click();
cy.get(commonSelectors.modalMessage).verifyVisibleElement(
"have.text",
commonText.deleteAppModalMessage(data.cloneAppName)
);
cy.get(
commonSelectors.buttonSelector(commonText.cancelButton)
).verifyVisibleElement("have.text", commonText.cancelButton);
cy.get(
commonSelectors.buttonSelector(commonText.modalYesButton)
).verifyVisibleElement("have.text", commonText.modalYesButton);
cancelModal(commonText.cancelButton);
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.deleteAppOption).click();
cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.cloneAppName);
cy.wait("@appLibrary");
cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.appName);
});
it("Should verify the app CRUD operation", () => {
const customLayout = {
desktop: { top: 100, left: 20 },
@ -369,10 +352,7 @@ describe("dashboard", () => {
cy.wait("@appLibrary");
cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.appName);
});
@ -493,10 +473,7 @@ describe("dashboard", () => {
cy.get(commonSelectors.allApplicationsLink).click();
cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.appName);
logout();
});

View file

@ -78,14 +78,16 @@ describe("Manage Groups", () => {
cy.createApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appCreatedToast
commonText.appCreatedToast,
false
);
cy.backToApps();
cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
commonText.appDeletedToast,
false
);
// Folder operations
@ -115,7 +117,8 @@ describe("Manage Groups", () => {
cy.get(commonSelectors.cloneAppButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
dashboardText.appClonedToast
dashboardText.appClonedToast,
false
);
// cy.get(commonSelectors.cancelButton).click();
cy.apiLogout();
@ -177,14 +180,16 @@ describe("Manage Groups", () => {
cy.createApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appCreatedToast
commonText.appCreatedToast,
false
);
cy.backToApps();
cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
commonText.appDeletedToast,
false
);
// Folder operations

View file

@ -204,10 +204,7 @@ describe("Manage Groups", () => {
cy.wait(2500);
cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
// Folder operations
createFolder(data.folderName);

View file

@ -2127,7 +2127,7 @@
"encrypted": false
},
"host": {
"value": "35.238.9.114",
"value": "9.234.17.31",
"encrypted": false
},
"port": {

View file

@ -585,7 +585,7 @@
"encrypted": false
},
"host": {
"value": "35.238.9.114",
"value": "9.234.17.31",
"encrypted": false
},
"port": {

View file

@ -1862,7 +1862,7 @@
"encrypted": false
},
"host": {
"value": "35.238.9.114",
"value": "9.234.17.31",
"encrypted": false
},
"port": {

View file

@ -2766,7 +2766,7 @@
"name": "restapiStaticUrlG",
"options": {
"method": "get",
"url": "http://34.66.166.236:4000/{{constants.gconstEndpoint}}",
"url": "http://20.29.40.108:4000/{{constants.gconstEndpoint}}",
"url_params": [
[
"",
@ -2814,7 +2814,7 @@
"name": "restapiUrlS",
"options": {
"method": "get",
"url": "http://34.66.166.236:4000/{{secrets.sconstEndpoint}}",
"url": "http://20.29.40.108:4000/{{secrets.sconstEndpoint}}",
"url_params": [
[
"",
@ -2908,7 +2908,7 @@
"name": "restapiUrlGS",
"options": {
"method": "get",
"url": "http://34.66.166.{{constants.gconst}}{{secrets.sconst}}/production",
"url": "http://20.29.40.{{constants.gconst}}{{secrets.sconst}}/production",
"url_params": [
[
"",
@ -3419,7 +3419,7 @@
"environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c",
"options": {
"url": {
"value": "http://34.66.166.236:4000/{{constants.gconstEndpoint}}",
"value": "http://20.29.40.108:4000/{{constants.gconstEndpoint}}",
"encrypted": false
},
"auth_type": {
@ -3540,7 +3540,7 @@
"environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c",
"options": {
"url": {
"value": "http://34.66.166.236:4000/{{secrets.sconstEndpoint}}",
"value": "http://20.29.40.108:4000/{{secrets.sconstEndpoint}}",
"encrypted": false
},
"auth_type": {
@ -3782,7 +3782,7 @@
"environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c",
"options": {
"url": {
"value": "http://34.66.166.{{constants.gconst}}{{secrets.sconst}}/production",
"value": "http://20.29.40.{{constants.gconst}}{{secrets.sconst}}/production",
"encrypted": false
},
"auth_type": {

View file

@ -101,11 +101,14 @@ export const navigateToAppEditor = (appName) => {
export const viewAppCardOptions = (appName) => {
cy.wait(1000);
cy.reloadAppForTheElement(appName);
cy.get(commonSelectors.appCard(appName))
.realHover()
.find(commonSelectors.appCardOptionsButton)
.realHover()
cy.contains("div", appName)
.parent()
.within(() => {
cy.get(commonSelectors.appCardOptionsButton).invoke("click");
cy.get(commonSelectors.appCardOptionsButton).click();
});
};
@ -185,8 +188,9 @@ export const searchUser = (email) => {
};
export const selectAppCardOption = (appName, appCardOption) => {
cy.wait(1000);
viewAppCardOptions(appName);
cy.get(appCardOption).should("be.visible").click({ force: true });
cy.get(appCardOption).should("be.visible").click();
};
export const navigateToDatabase = () => {

View file

@ -53,6 +53,8 @@ export const modifyAndVerifyAppCardIcon = (appName) => {
};
export const verifyAppDelete = (appName) => {
cy.get("body").should("exist").and("be.visible");
cy.get('[data-cy="dashboard-section-header"]').should("be.visible");
cy.get("body").then(($title) => {
if (!$title.text().includes(commonText.introductionMessage)) {
cy.clearAndType(commonSelectors.homePageSearchBar, appName);

View file

@ -239,7 +239,8 @@ export const createRestAPIQuery = (
key = "",
value = "",
url = "",
run = true
run = true,
kind = "restapi"
) => {
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
@ -247,7 +248,6 @@ export const createRestAPIQuery = (
Cookie: `tj_auth_token=${cookie.value}`,
};
cy.log(Cypress.env("appId"));
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`,
@ -255,13 +255,13 @@ export const createRestAPIQuery = (
}).then((response) => {
const editingVersionId = response.body.editing_version.id;
const data_source_id = Cypress.env(`${dsName}-id`);
const data_source_id = Cypress.env(kind);
const requestBody = {
app_id: Cypress.env("appId"),
app_version_id: editingVersionId,
name: queryName,
kind: "restapi",
kind: kind,
options: {
method: "get",
url: url,

View file

@ -18,79 +18,97 @@ export const createAndRunRestAPIQuery = (
method: "GET",
url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`,
headers,
}).then((response) => {
const editingVersionId = response.body.editing_version.id;
const data_source_id = Cypress.env(`${dsName}-id`);
const useJsonBody =
["POST", "PATCH", "PUT"].includes(method.toUpperCase()) &&
jsonBody !== null;
const queryOptions = {
method: method.toLowerCase(),
url: url + urlSuffix,
url_params: [["", ""]],
headers: headersList.length ? headersList : [["", ""]],
body: !useJsonBody && bodyList.length ? bodyList : [["", ""]],
json_body: useJsonBody ? jsonBody : null,
body_toggle: useJsonBody,
runOnPageLoad: run,
transformationLanguage: "javascript",
enableTransformation: false,
};
const requestBody = {
app_id: Cypress.env("appId"),
app_version_id: editingVersionId,
name: queryName,
kind: "restapi",
options: queryOptions,
data_source_id,
plugin_id: null,
};
}).then((appResponse) => {
const currentEnvironmentId = appResponse.body.editorEnvironment.id;
const editingVersionId = appResponse.body.editing_version.id;
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`,
method: "GET",
url: `${Cypress.env("server_host")}/api/data-sources/${Cypress.env("workspaceId")}/environments/${currentEnvironmentId}/versions/${editingVersionId}`,
headers,
body: requestBody,
}).then((createResponse) => {
expect(createResponse.status).to.equal(201);
const queryId = createResponse.body.id;
cy.log("Query created successfully:", queryId);
}).then((dsResponse) => {
expect(dsResponse.status).to.eq(200);
const createdOptions = createResponse.body.options;
expect(createdOptions.method).to.equal(queryOptions.method);
expect(createdOptions.url).to.equal(queryOptions.url);
expect(createdOptions.headers).to.deep.equal(queryOptions.headers);
const dataSource = dsResponse.body.data_sources.find(
(ds) => ds.name === dsName
);
if (useJsonBody) {
expect(createdOptions.json_body).to.deep.equal(
queryOptions.json_body
);
expect(createdOptions.body_toggle).to.equal(true);
} else {
expect(createdOptions.body).to.deep.equal(queryOptions.body);
expect(createdOptions.body_toggle).to.equal(false);
if (!dataSource) {
throw new Error(`Data source '${dsName}' not found.`);
}
expect(createdOptions.runOnPageLoad).to.equal(run);
cy.log("Metadata verified successfully");
if (run) {
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`,
headers,
}).then((runResponse) => {
expect([200, 201]).to.include(runResponse.status);
cy.log("Query executed successfully:", runResponse.body);
if (runResponse.body?.data.id) {
cy.writeFile("cypress/fixtures/restAPI/storedId.json", {
id: runResponse.body.data.id,
});
cy.log("Stored ID:", runResponse.body.data.id);
}
});
}
const data_source_id = dataSource.id;
const useJsonBody =
["POST", "PATCH", "PUT"].includes(method.toUpperCase()) &&
jsonBody !== null;
const queryOptions = {
method: method.toLowerCase(),
url: url + urlSuffix,
url_params: [["", ""]],
headers: headersList.length ? headersList : [["", ""]],
body: !useJsonBody && bodyList.length ? bodyList : [["", ""]],
json_body: useJsonBody ? jsonBody : null,
body_toggle: useJsonBody,
runOnPageLoad: run,
transformationLanguage: "javascript",
enableTransformation: false,
};
const requestBody = {
app_id: Cypress.env("appId"),
app_version_id: editingVersionId,
name: queryName,
kind: "restapi",
options: queryOptions,
data_source_id,
plugin_id: null,
};
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`,
headers,
body: requestBody,
}).then((createResponse) => {
expect(createResponse.status).to.equal(201);
const queryId = createResponse.body.id;
cy.log("Query created successfully:", queryId);
const createdOptions = createResponse.body.options;
expect(createdOptions.method).to.equal(queryOptions.method);
expect(createdOptions.url).to.equal(queryOptions.url);
expect(createdOptions.headers).to.deep.equal(queryOptions.headers);
if (useJsonBody) {
expect(createdOptions.json_body).to.deep.equal(
queryOptions.json_body
);
expect(createdOptions.body_toggle).to.equal(true);
} else {
expect(createdOptions.body).to.deep.equal(queryOptions.body);
expect(createdOptions.body_toggle).to.equal(false);
}
expect(createdOptions.runOnPageLoad).to.equal(run);
cy.log("Metadata verified successfully");
if (run) {
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`,
headers,
}).then((runResponse) => {
expect([200, 201]).to.include(runResponse.status);
cy.log("Query executed successfully:", runResponse.body);
if (runResponse.body?.data.id) {
cy.writeFile("cypress/fixtures/restAPI/storedId.json", {
id: runResponse.body.data.id,
});
cy.log("Stored ID:", runResponse.body.data.id);
}
});
}
});
});
});
});

View file

@ -115,8 +115,8 @@ export const verifyDuplicateVersion = (newVersion = [], version) => {
cy.get(appVersionSelectors.createNewVersionButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
// appVersionText.versionNameAlreadyExists
"Already exists!"
appVersionText.versionNameAlreadyExists
// "Already exists!"
);
};

View file

@ -38,7 +38,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget supervisor
RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor
# Install Instantclient Basic Light Oracle and Dependencies
WORKDIR /opt/oracle
@ -54,9 +54,6 @@ ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21
WORKDIR /
RUN mkdir -p /app /var/log/supervisor
COPY /deploy/docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# copy npm scripts
COPY --from=builder /app/package.json ./app/package.json
# copy plugins dependencies
@ -77,32 +74,64 @@ COPY --from=builder /app/server/dist ./app/server/dist
WORKDIR /app
# Install PostgreSQL
USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian"
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \
psql -c "create role tooljet with login superuser password 'postgres';"
USER root
# Explicitly create PG main directory with correct ownership
RUN mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
RUN mkdir -p /var/log/supervisor /var/run/postgresql && \
chown -R postgres:postgres /var/run/postgresql /var/log/supervisor
# Remove existing data and create directory with proper ownership
RUN rm -rf /var/lib/postgresql/13/main && \
mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
# Initialize PostgreSQL
RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main"
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
RUN echo "[supervisord] \n" \
"nodaemon=true \n" \
"user=root \n" \
"\n" \
"[program:postgrest] \n" \
"command=/bin/postgrest \n" \
"autostart=true \n" \
"autorestart=true \n" \
"\n" \
"[program:tooljet] \n" \
"user=root \n" \
"command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \
"autostart=true \n" \
"autorestart=true \n" \
"stderr_logfile=/dev/stdout \n" \
"stderr_logfile_maxbytes=0 \n" \
"stdout_logfile=/dev/stdout \n" \
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# ENV defaults
ENV TOOLJET_HOST=http://localhost \
PORT=80 \
NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \
PG_DB=tooljet_production \
PG_USER=tooljet \
PG_USER=postgres \
PG_PASS=postgres \
PG_HOST=localhost \
ENABLE_TOOLJET_DB=true \
TOOLJET_DB_HOST=localhost \
TOOLJET_DB_USER=tooljet \
TOOLJET_DB_USER=postgres \
TOOLJET_DB_PASS=postgres \
TOOLJET_DB=tooljet_db \
PGRST_HOST=http://localhost:3000 \
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
ORM_LOGGING=true \
@ -110,4 +139,7 @@ ENV TOOLJET_HOST=http://localhost \
HOME=/home/appuser \
TERM=xterm
CMD ["/usr/bin/supervisord"]
RUN chmod +x ./server/scripts/preview.sh
# Set the entrypoint
ENTRYPOINT ["./server/scripts/preview.sh"]

View file

@ -9,7 +9,7 @@ WORKDIR /app
ARG CUSTOM_GITHUB_TOKEN
ARG BRANCH_NAME
# Clone and checkout the frontend repository
# Clone and checkout the frontend repositorys
RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
RUN git config --global http.version HTTP/1.1
@ -66,7 +66,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget supervisor
RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor
# Install Instantclient Basic Light Oracle and Dependencies
WORKDIR /opt/oracle
@ -82,9 +82,6 @@ ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21
WORKDIR /
RUN mkdir -p /app /var/log/supervisor
COPY /deploy/docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# copy npm scripts
COPY --from=builder /app/package.json ./app/package.json
# copy plugins dependencies
@ -106,38 +103,73 @@ COPY --from=builder /app/server/dist ./app/server/dist
WORKDIR /app
# ENV defaults
# Install PostgreSQL
USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian"
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \
psql -c "create role tooljet with login superuser password 'postgres';"
USER root
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor --fix-missing
# Explicitly create PG main directory with correct ownership
RUN mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
RUN mkdir -p /var/log/supervisor /var/run/postgresql && \
chown -R postgres:postgres /var/run/postgresql /var/log/supervisor
# Remove existing data and create directory with proper ownership
RUN rm -rf /var/lib/postgresql/13/main && \
mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
# Initialize PostgreSQL
RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main"
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
RUN echo "[supervisord] \n" \
"nodaemon=true \n" \
"user=root \n" \
"\n" \
"[program:postgrest] \n" \
"command=/bin/postgrest \n" \
"autostart=true \n" \
"autorestart=true \n" \
"\n" \
"[program:tooljet] \n" \
"user=root \n" \
"command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \
"autostart=true \n" \
"autorestart=true \n" \
"stderr_logfile=/dev/stdout \n" \
"stderr_logfile_maxbytes=0 \n" \
"stdout_logfile=/dev/stdout \n" \
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# ENV defaults
ENV TOOLJET_HOST=http://localhost \
PORT=80 \
NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \
PG_DB=tooljet_production \
PG_USER=tooljet \
PG_USER=postgres \
PG_PASS=postgres \
PG_HOST=localhost \
ENABLE_TOOLJET_DB=true \
TOOLJET_DB_HOST=localhost \
TOOLJET_DB_USER=tooljet \
TOOLJET_DB_USER=postgres \
TOOLJET_DB_PASS=postgres \
TOOLJET_DB=tooljet_db \
PGRST_HOST=http://localhost:3000 \
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \
PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
ORM_LOGGING=true \
DEPLOYMENT_PLATFORM=docker:local \
REDIS_PASS= \
HOME=/home/appuser \
TERM=xterm
CMD ["/usr/bin/supervisord"]
RUN chmod +x ./server/scripts/preview.sh
# Set the entrypoint
ENTRYPOINT ["./server/scripts/preview.sh"]

View file

@ -1 +1 @@
3.11.0
3.12.1

@ -1 +1 @@
Subproject commit 518f3334b12a83785fd37dd53b0245d72848211a
Subproject commit 1b77a556709211daed8924821383db9dccc95eb5

View file

@ -58,6 +58,7 @@
"dotenv": "^16.0.3",
"draft-js": "^0.11.7",
"draft-js-export-html": "^1.4.1",
"draft-js-import-html": "^1.4.1",
"driver.js": "^0.9.8",
"emoji-mart": "^5.5.2",
"file-loader": "^6.2.0",

View file

@ -232,6 +232,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
const childTabId = componentParentId.split('-').at(-1);
if (componentParentId === `${parentId}-${childTabId}`) {
childComponent.isParentTabORCalendar = true;
childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId);
childComponents.push(childComponent);
// Recursively find children of the current child component
const childrenOfChild = getAllChildComponents(allComponents, componentId);
@ -242,6 +243,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
if (componentParentId === parentId) {
let childComponent = deepClone(allComponents[componentId]);
childComponent.id = componentId;
childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId);
childComponents.push(childComponent);
// Recursively find children of the current child component

View file

@ -0,0 +1,27 @@
/* eslint-disable import/no-unresolved */
import React from 'react';
import { openSearchPanel } from '@codemirror/search';
import './SearchBox.scss';
import { Button as ButtonComponent } from '@/components/ui/Button/Button.jsx';
export const CodeHinterBtns = ({ view, isPanelOpen, renderCopilot }) => {
return (
<div
className="d-flex tw-flex-col align-items-end tw-gap-[2.5px] w-100 position-absolute tw-pt-[4px] tw-pr-[4px]"
style={{ top: isPanelOpen ? '44px' : 0 }}
>
{!isPanelOpen && (
<ButtonComponent
iconOnly
trailingIcon="search01"
size="small"
variant="outline"
ariaLabel="Open search panel"
className="codehinter-search-btn"
onClick={() => openSearchPanel(view)}
/>
)}
{renderCopilot && renderCopilot()}
</div>
);
};

View file

@ -20,10 +20,12 @@ import { PreviewBox } from './PreviewBox';
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { syntaxTree } from '@codemirror/language';
import { search, searchKeymap, searchPanelOpen } from '@codemirror/search';
import { handleSearchPanel, SearchBtn } from './SearchBox';
import { handleSearchPanel } from './SearchBox';
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
import { isInsideParent } from './utils';
import { CodeHinterBtns } from './CodehinterOverlayTriggers';
const langSupport = Object.freeze({
javascript: javascript(),
@ -66,7 +68,7 @@ const MultiLineCodeEditor = (props) => {
const context = useContext(CodeHinterContext);
const { suggestionList } = createReferencesLookup(context, true);
const { suggestionList: paramList } = createReferencesLookup(context, true);
const currentValueRef = useRef(initialValue);
@ -74,6 +76,7 @@ const MultiLineCodeEditor = (props) => {
const [editorView, setEditorView] = React.useState(null);
const [isSearchPanelOpen, setIsSearchPanelOpen] = React.useState(false);
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline');
const handleOnBlur = () => {
@ -146,8 +149,29 @@ const MultiLineCodeEditor = (props) => {
return suggestion.hint.includes(nearestSubstring);
});
const localVariables = new Set();
// Traverse the syntax tree to extract variable declarations
syntaxTree(context.state).iterate({
enter: (node) => {
// JavaScript: Detect variable declarations (var, let, const)
if (node.name === 'VariableDefinition') {
const varName = context.state.sliceDoc(node.from, node.to);
if (varName && varName.startsWith(nearestSubstring)) localVariables.add(varName);
}
},
});
// Convert Set to an array of completion suggestions
const localVariableSuggestions = [...localVariables].map((varName) => ({
hint: varName,
type: 'variable',
}));
const suggestionList = paramList.filter((paramSuggestion) => paramSuggestion.hint.includes(nearestSubstring));
const suggestions = generateHints(
[...JSLangHints, ...autoSuggestionList, ...suggestionList],
[...localVariableSuggestions, ...JSLangHints, ...autoSuggestionList, ...suggestionList],
null,
nearestSubstring
).map((hint) => {
@ -204,6 +228,7 @@ const MultiLineCodeEditor = (props) => {
return {
from: context.pos,
options: [...suggestions],
filter: false,
};
}
@ -237,7 +262,7 @@ const MultiLineCodeEditor = (props) => {
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), []);
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [paramList]);
const { handleTogglePopupExapand, isOpen, setIsOpen, forceUpdate } = portalProps;
let cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : props.cyLabel;
@ -258,7 +283,7 @@ const MultiLineCodeEditor = (props) => {
ref={wrapperRef}
>
<div className={`${className} ${darkMode && 'cm-codehinter-dark-themed'}`}>
<SearchBtn view={editorView} />
<CodeHinterBtns view={editorView} isPanelOpen={isSearchPanelOpen} renderCopilot={renderCopilot} />
<CodeHinter.PopupIcon
callback={handleTogglePopupExapand}
icon="portal-open"
@ -266,7 +291,6 @@ const MultiLineCodeEditor = (props) => {
isMultiEditor={true}
isQueryManager={isInsideQueryPane}
/>
{renderCopilot && renderCopilot()}
<CodeHinter.Portal
isCopilotEnabled={false}
@ -326,12 +350,7 @@ const MultiLineCodeEditor = (props) => {
readOnly={readOnly}
editable={editable} //for transformations in query manager
onCreateEditor={(view) => setEditorView(view)}
onUpdate={(view) => {
const icon = document.querySelector('.codehinter-search-btn');
if (searchPanelOpen(view.state)) {
icon.style.display = 'none';
} else icon.style.display = 'block';
}}
onUpdate={(view) => setIsSearchPanelOpen(searchPanelOpen(view.state))}
/>
</div>
{showPreview && (

View file

@ -9,7 +9,6 @@ import {
findPrevious,
replaceNext,
replaceAll,
openSearchPanel,
} from '@codemirror/search';
import './SearchBox.scss';
import InputComponent from '@/components/ui/Input/Index.jsx';
@ -162,22 +161,3 @@ function SearchPanel({ view }) {
</div>
);
}
export const SearchBtn = ({ view }) => {
return (
<div
className="d-flex justify-content-end w-100 position-absolute tw-pt-[3px] tw-pr-[4px] codehinter-search-btn-wrapper"
style={{ top: 0 }}
>
<ButtonComponent
iconOnly
trailingIcon="search01"
size="small"
variant="outline"
ariaLabel="Open search panel"
className="codehinter-search-btn"
onClick={() => openSearchPanel(view)}
/>
</div>
);
};

View file

@ -44,7 +44,5 @@
}
.code-hinter-wrapper .codehinter-search-btn {
display: block;
padding-top: 1px;
z-index: 10000;
z-index: 1000;
}

View file

@ -1,12 +1,18 @@
/* eslint-disable import/no-unresolved */
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState, useContext } from 'react';
import { PreviewBox } from './PreviewBox';
import { ToolTip } from '@/Editor/Inspector/Elements/Components/ToolTip';
import { useTranslation } from 'react-i18next';
import { camelCase, isEmpty, noop, get } from 'lodash';
import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { autocompletion, completionKeymap, completionStatus, acceptCompletion } from '@codemirror/autocomplete';
import {
autocompletion,
completionKeymap,
completionStatus,
acceptCompletion,
startCompletion,
} from '@codemirror/autocomplete';
import { defaultKeymap } from '@codemirror/commands';
import { keymap } from '@codemirror/view';
import FxButton from '../CodeBuilder/Elements/FxButton';
@ -22,6 +28,8 @@ import CodeHinter from './CodeHinter';
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext';
import { createReferencesLookup } from '@/_stores/utils';
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
@ -73,6 +81,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
if (typeof initialValue === 'string' && (initialValue?.includes('components') || initialValue?.includes('queries'))) {
newInitialValue = replaceIdsWithName(initialValue);
}
//! Re render the component when the componentName changes as the initialValue is not updated
// const { variablesExposedForPreview } = useContext(EditorContext) || {};
@ -199,9 +208,14 @@ const EditorInput = ({
wrapperRef,
showSuggestions,
}) => {
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
const codeHinterContext = useContext(CodeHinterContext);
const { suggestionList: paramHints } = createReferencesLookup(codeHinterContext, true);
const getSuggestions = useStore((state) => state.getSuggestions, shallow);
const [codeMirrorView, setCodeMirrorView] = useState(undefined);
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline');
const isInsideQueryManager = useMemo(
@ -209,16 +223,16 @@ const EditorInput = ({
[wrapperRef.current]
);
function autoCompleteExtensionConfig(context) {
const hints = getSuggestions();
const hintsWithoutParamHints = getSuggestions();
const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
const allHints = {
...hints,
appHints: [...hints.appHints, ...serverHints],
};
let word = context.matchBefore(/\w*/);
const hints = {
...hintsWithoutParamHints,
appHints: [...hintsWithoutParamHints.appHints, ...serverHints, ...paramHints],
};
const totalReferences = (context.state.doc.toString().match(/{{/g) || []).length;
let queryInput = context.state.doc.toString();
@ -247,17 +261,18 @@ const EditorInput = ({
queryInput = '{{' + currentWord + '}}';
}
let completions = getAutocompletion(queryInput, validationType, allHints, totalReferences, originalQueryInput);
let completions = getAutocompletion(queryInput, validationType, hints, totalReferences, originalQueryInput);
return {
from: word.from,
options: completions,
validFor: /^\{\{.*\}\}$/,
filter: false,
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager]);
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager, paramHints]);
const autoCompleteConfig = autocompletion({
override: [overRideFunction],
@ -424,6 +439,9 @@ const EditorInput = ({
ref={previewRef}
>
<CodeMirror
onCreateEditor={(view) => {
setCodeMirrorView(view);
}}
value={currentValue}
placeholder={placeholder}
height={isInsideQueryPane ? '100%' : showLineNumbers ? '400px' : '100%'}
@ -460,11 +478,16 @@ const EditorInput = ({
theme={theme}
indentWithTab={false}
readOnly={disabled}
onKeyDown={(event) => {
if (event.key === 'Backspace') {
startCompletion(codeMirrorView);
}
}}
/>
</div>
</ErrorBoundary>
</CodeHinter.Portal>
</div>
</ErrorBoundary >
</CodeHinter.Portal >
</div >
);
};

View file

@ -67,7 +67,8 @@ export const getAutocompletion = (input, fieldType, hints, totalReferences = 1,
originalQueryInput,
searchInput
);
return orderSuggestions(suggestions, fieldType);
return suggestions;
};
function orderSuggestions(suggestions, validationType) {
@ -90,10 +91,18 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
const hasDepth = currentWord.includes('.');
const lastDepth = getLastSubstring(currentWord);
const displayLabel = getLastDepth(displayedHint);
let displayLabel = getLastDepth(displayedHint);
if (type != 'js_method') {
const currentWordDepth = currentWord.split('.').length;
displayLabel = hint
.split('.')
.slice(currentWordDepth - 1)
.join('.');
}
return {
displayLabel: lastDepth === '' ? displayedHint : displayLabel,
displayLabel,
label: displayedHint,
info: displayedHint,
type: type === 'js_method' ? 'js_methods' : type?.toLowerCase(),
@ -154,40 +163,24 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
};
function filterHintsByDepth(input, hints) {
if (input === '') return hints;
const inputParts = input.split('.');
const inputDepth = inputParts.length + 1;
const inputDepth = input.includes('.') ? input.split('.').length : 0;
const filteredHints = hints.filter((cm) => {
const hintParts = cm.hint.split('.');
let shouldInclude =
(cm.hint.startsWith(input) && hintParts.length === inputDepth + 1) ||
(cm.hint.startsWith(input) && hintParts.length === inputDepth);
const shouldFuzzyMatch = !shouldInclude ? hintParts.length > inputDepth : false;
if (shouldFuzzyMatch) {
// fuzzy match
let matchedDepth = -1;
for (let i = 0; i < hintParts.length; i++) {
if (hintParts[i].includes(input)) {
matchedDepth = i;
break;
}
}
if (matchedDepth !== -1) {
shouldInclude = hintParts.length === matchedDepth + 1;
}
} else if (input.endsWith('.')) {
shouldInclude = cm.hint.startsWith(input) && hintParts.length === inputDepth;
}
return shouldInclude;
const hintsWithDepth = hints.map((hint) => {
const hintParts = hint.hint.split('.');
return {
...hint,
depth: hintParts.length,
};
});
return filteredHints;
const filteredHints = hintsWithDepth.filter((hint) => {
return hint.depth <= inputDepth;
});
const sortedHints = filteredHints.sort((hint1, hint2) => hint1.depth - hint2.depth);
return sortedHints;
}
export function findNearestSubstring(inputStr, currentCurosorPos) {

View file

@ -1,8 +1,20 @@
import React from 'react';
import React, { useState } from 'react';
import Select from '@/_ui/Select';
import { decodeEntities } from '@/_helpers/utils';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleased }) => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
usePopoverObserver(
document.getElementsByClassName('query-details')[0],
document.querySelector('.change-data-source-select.react-select__control'),
document.querySelector('.change-data-source-select.react-select__menu'),
isMenuOpen,
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'block'),
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'none')
);
return (
<Select
className="w-100"
@ -14,6 +26,13 @@ export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleas
}}
useMenuPortal={true}
isDisabled={isVersionReleased}
customClassPrefix="change-data-source-select"
onMenuOpen={() => {
setIsMenuOpen(true);
}}
onMenuClose={() => {
setIsMenuOpen(false);
}}
/>
);
};

View file

@ -254,7 +254,7 @@ const RunButton = ({ buttonLoadingState }) => {
<ButtonComponent
size="medium"
variant="secondary"
onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)}
onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true, undefined, true)}
leadingIcon="play01"
disabled={isInDraft}
isLoading={isLoading}

View file

@ -31,6 +31,9 @@ class Restapi extends React.Component {
codeHinterHeight: 32, // Default height
};
this.codeHinterRef = React.createRef();
this.isMenuOpenRef = React.createRef();
this.prevIsMenuOpenRef = React.createRef(false);
this.intersectionObserver = null;
this.resizeObserver = null;
}
@ -47,6 +50,9 @@ class Restapi extends React.Component {
if (this.codeHinterRef.current && !this.resizeObserver) {
this.setupResizeObserver();
}
if (!this.intersectionObserver) {
this.setupIntersectionObserver();
}
}
componentDidMount() {
@ -75,6 +81,7 @@ class Restapi extends React.Component {
}, 1000);
this.setupResizeObserver();
this.setupIntersectionObserver();
} catch (error) {
console.log(error);
}
@ -84,6 +91,9 @@ class Restapi extends React.Component {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
}
setupResizeObserver() {
@ -132,6 +142,33 @@ class Restapi extends React.Component {
this.resizeObserver.observe(element);
}
setupIntersectionObserver() {
const container = document.getElementsByClassName('query-details')[0];
const trigger = document.querySelector('.restapi-method-select.react-select__control');
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
this.intersectionObserver = new IntersectionObserver(
([entry]) => {
const popover = document.querySelector('.restapi-method-select.react-select__menu');
if (entry.isIntersecting) {
if (this.prevIsMenuOpenRef.current) {
popover.style.display = 'block';
this.prevIsMenuOpenRef.current = false;
}
} else if (this.isMenuOpenRef.current) {
popover.style.display = 'none';
this.prevIsMenuOpenRef.current = true;
}
},
{ root: container, threshold: [0.5] }
);
this.intersectionObserver.observe(trigger);
}
initizalizeRetryNetworkErrorsToggle = () => {
const isRetryNetworkErrorToggleUnused = this.props.options.retry_network_errors === null;
if (isRetryNetworkErrorToggleUnused) {
@ -287,6 +324,13 @@ class Restapi extends React.Component {
height={32}
styles={this.customSelectStyles(this.props.darkMode, 91)}
useCustomStyles={true}
customClassPrefix="restapi-method-select"
onMenuOpen={() => {
this.isMenuOpenRef.current = true;
}}
onMenuClose={() => {
this.isMenuOpenRef.current = false;
}}
/>
</div>
<div

View file

@ -53,7 +53,11 @@ export const BulkUploadPrimaryKey = () => {
<div className="field flex-grow-1 minw-400-w-400">
<CodeHinter
type="basic"
initialValue={`{{${JSON.stringify(bulkUpdatePrimaryKey?.rows_update ?? [])}}}`}
initialValue={
bulkUpdatePrimaryKey?.rows_update
? `{{${JSON.stringify(bulkUpdatePrimaryKey?.rows_update ?? [])}}}`
: null
}
className="codehinter-plugins"
placeholder="{{ [ { 'column1': 'value', ... } ] }}"
onChange={(newValue) => {

View file

@ -0,0 +1,78 @@
import React, { useContext, useEffect } from 'react';
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
import { resolveReferences } from '@/AppBuilder/CodeEditor/utils';
import CodeHinter from '@/AppBuilder/CodeEditor';
export const BulkUpsertPrimaryKey = () => {
const {
columns,
bulkUpsertPrimaryKey,
handleBulkUpsertRowsOptionChanged,
handlePrimaryKeyOptionChangedForBulkUpsert,
} = useContext(TooljetDatabaseContext);
useEffect(() => {
const primaryKeys = columns.reduce((acc, column) => {
if (column?.keytype === 'PRIMARY KEY' || column?.isPrimaryKey) {
acc.push(column?.accessor);
}
return acc;
}, []);
if (primaryKeys.length > 0) {
handlePrimaryKeyOptionChangedForBulkUpsert(primaryKeys);
}
}, [columns]);
const handleRowsChange = (value) => {
handleBulkUpsertRowsOptionChanged(value);
};
return (
<div className="tab-content-wrapper tj-db-field-wrapper mt-2 d-flex flex-column custom-gap-16">
<div className="field-container d-flex tooljetdb-worflow-operations">
<label className="form-label flex-shrink-0">Primary key</label>
<div
className="field flex-grow-1 minw-400-w-400 px-1"
style={{ height: '28px', background: 'var(--controls-switch-tag)', borderRadius: '6px' }}
>
<input
type="text"
value={bulkUpsertPrimaryKey?.primary_key?.join(', ') || ''}
style={{
width: '100%',
height: '100%',
border: '0',
color: 'var(--text-placeholder)',
background: 'transparent',
}}
disabled
placeholder={''}
/>
</div>
</div>
<div className="field-container d-flex tooljetdb-worflow-operations">
<label className="form-label flex-shrink-0" data-cy="">
Rows to upsert
</label>
<div className="field flex-grow-1 minw-400-w-400">
<CodeHinter
type="basic"
initialValue={
bulkUpsertPrimaryKey?.rows
? typeof bulkUpsertPrimaryKey?.rows === 'string'
? bulkUpsertPrimaryKey?.rows
: JSON.stringify(bulkUpsertPrimaryKey?.rows)
: bulkUpsertPrimaryKey?.rows
}
className="codehinter-plugins"
placeholder="{{ [ { 'column1': 'value', ... } ] }}"
onChange={handleRowsChange}
/>
</div>
</div>
</div>
);
};
export default BulkUpsertPrimaryKey;

View file

@ -1,5 +1,4 @@
import CodeHinter from '@/AppBuilder/CodeEditor';
import { resolveReferences } from '@/Editor/CodeEditor/utils';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import Trash from '@/_ui/Icon/solidIcons/Trash';
import React from 'react';
@ -43,8 +42,7 @@ const RenderColumnUI = ({
placeholder="key"
onChange={(newValue) => {
if (isJSonTypeColumn) {
const [_, __, resolvedValue] = resolveReferences(`{{${newValue}}}`);
handleValueChange(resolvedValue);
handleValueChange(newValue);
} else {
handleValueChange(newValue);
}

View file

@ -220,7 +220,9 @@ function DataSourceSelect({
if (isFirstPageLoaded && offset >= totalRecords) return;
if (foreignKeys.length < 1) return;
setIsLoadingFKDetails(true);
const referencedColumns = foreignKeys.find((item) => item.column_names[0] === cellColumnName);
const referencedColumns = Array.isArray(foreignKeys)
? foreignKeys.find((item) => item.column_names[0] === cellColumnName)
: undefined;
if (!referencedColumns?.referenced_column_names?.length) return;
const selectQuery = new PostgrestQueryBuilder();
@ -709,7 +711,8 @@ const MenuList = ({
...props
}) => {
const menuListStyles = getStyles('menuList', props);
const referencedColumnDetails = foreignKeys?.find((item) => item.column_names[0] === cellColumnName);
const referencedColumnDetails =
Array.isArray(foreignKeys) && foreignKeys?.find((item) => item?.column_names[0] === cellColumnName);
const handleNavigateToReferencedTable = () => {
const data = {

View file

@ -16,6 +16,7 @@ import { getPrivateRoute } from '@/_helpers/routes';
import { useNavigate } from 'react-router-dom';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import { BulkUploadPrimaryKey } from './BulkUploadPrimaryKey';
import BulkUpsertPrimaryKey from './BulkUpsertPrimaryKey';
import './styles.scss';
import CodeHinter from '@/AppBuilder/CodeEditor';
@ -46,6 +47,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
const [tableForeignKeyInfo, setTableForeignKeyInfo] = useState({});
const [bulkUpdatePrimaryKey, setBulkUpdatePrimaryKey] = useState(() => options['bulk_update_with_primary_key'] || {});
const [bulkUpsertPrimaryKey, setBulkUpsertPrimaryKey] = useState(() => options['bulk_upsert_with_primary_key'] || {});
const joinOptions = options['join_table']?.['joins'] || [
{ conditions: { conditionsList: [{ leftField: { table: selectedTableId } }] } },
@ -196,6 +198,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bulkUpdatePrimaryKey]);
useEffect(() => {
mounted && optionchanged('bulk_upsert_with_primary_key', bulkUpsertPrimaryKey);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bulkUpsertPrimaryKey]);
useEffect(() => {
mounted && optionchanged('update_rows', updateRowsOptions);
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -234,10 +241,18 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
setBulkUpdatePrimaryKey((prev) => ({ ...prev, rows_update: value }));
};
const handleBulkUpsertRowsOptionChanged = (value) => {
setBulkUpsertPrimaryKey((prev) => ({ ...prev, rows: value }));
};
const handlePrimaryKeyOptionChangedForBulkUpdate = (value) => {
setBulkUpdatePrimaryKey((prev) => ({ ...prev, primary_key: value }));
};
const handlePrimaryKeyOptionChangedForBulkUpsert = (value) => {
setBulkUpsertPrimaryKey((prev) => ({ ...prev, primary_key: value }));
};
const loadTableInformation = async (tableId, isNewTableAdded) => {
const tableDetails = findTableDetails(tableId);
if (tableDetails?.table_name && !tableInfo[tableDetails?.table_name]) {
@ -340,8 +355,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
tableForeignKeyInfo,
setTableForeignKeyInfo,
bulkUpdatePrimaryKey,
bulkUpsertPrimaryKey,
handleBulkUpdateWithPrimaryKeysRowsUpdateOptionChanged,
handleBulkUpsertRowsOptionChanged,
handlePrimaryKeyOptionChangedForBulkUpdate,
handlePrimaryKeyOptionChangedForBulkUpsert,
}),
[
organizationId,
@ -357,6 +375,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
joinOrderByOptions,
selectedTableId,
bulkUpdatePrimaryKey,
bulkUpsertPrimaryKey,
]
);
@ -517,6 +536,8 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
return JoinTable;
case 'bulk_update_with_primary_key':
return BulkUploadPrimaryKey;
case 'bulk_upsert_with_primary_key':
return BulkUpsertPrimaryKey;
}
};
@ -527,6 +548,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
{ label: 'Delete rows', value: 'delete_rows' },
{ label: 'Join tables', value: 'join_tables' },
{ label: 'Bulk update with primary key', value: 'bulk_update_with_primary_key' },
{ label: 'Bulk upsert with primary key', value: 'bulk_upsert_with_primary_key' },
];
const ComponentToRender = getComponent(operation);

View file

@ -5,14 +5,25 @@ import CodeHinter from '@/AppBuilder/CodeEditor';
import './workflows-query.scss';
import { v4 as uuidv4 } from 'uuid';
import useStore from '@/AppBuilder/_stores/store';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
export function Workflows({ options, optionsChanged, currentState }) {
const [workflowOptions, setWorkflowOptions] = useState([]);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined);
const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]);
const appId = useStore((state) => state.app.appId);
usePopoverObserver(
document.getElementsByClassName('query-details')[0],
document.querySelector('.workflow-select.react-select__control'),
document.querySelector('.workflow-select.react-select__menu'),
isMenuOpen,
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'block'),
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'none')
);
useEffect(() => {
appsService.getWorkflows(appId).then(({ workflows }) => {
setWorkflowOptions(
@ -50,6 +61,13 @@ export function Workflows({ options, optionsChanged, currentState }) {
customWrap={true}
width="300px"
menuPlacement="bottom"
customClassPrefix="workflow-select"
onMenuOpen={() => {
setIsMenuOpen(true);
}}
onMenuClose={() => {
setIsMenuOpen(false);
}}
/>
<label className="my-2">Params</label>
<div className="grid"></div>

View file

@ -0,0 +1,538 @@
import React, { useState, useEffect } from 'react';
import Accordion from '@/_ui/Accordion';
import { EventManager } from '../EventManager';
import { renderElement } from '../Utils';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import List from '@/ToolJetUI/List/List';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import useStore from '@/AppBuilder/_stores/store';
import CodeHinter from '@/AppBuilder/CodeEditor';
import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton';
import ListGroup from 'react-bootstrap/ListGroup';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import SortableList from '@/_components/SortableList';
import Trash from '@/_ui/Icon/solidIcons/Trash';
import { shallow } from 'zustand/shallow';
import Switch from '@/Editor/CodeBuilder/Elements/Switch';
import { usePrevious } from '@dnd-kit/utilities';
export function Steps({ componentMeta, darkMode, ...restProps }) {
const {
layoutPropertyChanged,
component,
dataQueries,
paramUpdated,
currentState,
eventsChanged,
apps,
allComponents,
pages,
} = restProps;
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const isDynamicOptionsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
const variant = component?.component?.definition?.properties?.variant?.value;
const prevVariant = usePrevious(variant)
console.log("variant", component?.component?.definition);
const [options, setOptions] = useState([]);
const [hoveredOptionIndex, setHoveredOptionIndex] = useState(null);
let properties = [];
let additionalActions = [];
let optionsProperties = [];
for (const [key] of Object.entries(componentMeta?.properties)) {
if (componentMeta?.properties[key]?.section === 'additionalActions') {
additionalActions.push(key);
} else if (componentMeta?.properties[key]?.accordian === 'Options') {
optionsProperties.push(key);
} else {
properties.push(key);
}
}
// the default style of "number" & "titles" type are different for completed label
// TODO: Need to revisit this logic when text custom themes are implemented
useEffect(() => {
const completedLabelColor = component?.component?.definition?.styles?.completedLabel?.value;
if (variant !== prevVariant) {
if (variant === "numbers" && completedLabelColor === "#1B1F24") {
paramUpdated({ name: 'completedLabel' }, 'value', "#FFFFFF", 'styles', false, {});
} else if (variant === "titles" && completedLabelColor === "#FFFFFF") {
paramUpdated({ name: 'completedLabel' }, 'value', "#1B1F24", 'styles', false, {});
}
}
}, [variant])
const getItemStyle = (isDragging, draggableStyle) => ({
userSelect: 'none',
...draggableStyle,
});
const updateAllOptionsParams = (options, props) => {
paramUpdated({ name: 'steps' }, 'value', options, 'properties', false, props);
};
const generateNewOptions = () => {
let found = false;
let label = '';
let currentNumber = options.length + 1;
while (!found) {
label = `step ${currentNumber}`;
if (options.find((option) => option.name === label) === undefined) {
found = true;
}
currentNumber += 1;
}
return {
name: label,
id: currentNumber - 1,
tooltip: label,
visible: { value: '{{true}}' },
disabled: { value: '{{false}}' },
};
};
const handleAddOption = () => {
let _option = generateNewOptions();
const _items = [...options, _option];
setOptions(_items);
updateAllOptionsParams(_items);
};
const handleDeleteOption = (index) => {
const _items = options.filter((option, i) => i !== index);
setOptions(_items);
updateAllOptionsParams(_items, { isParamFromDropdownOptions: true });
};
const handleLabelChange = (propertyName, value, index) => {
const _options = options.map((option, i) => {
if (i === index) {
return {
...option,
[propertyName]: value,
};
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
};
const reorderOptions = async (startIndex, endIndex) => {
const result = [...options];
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
setOptions(result);
updateAllOptionsParams(result);
};
const onDragEnd = ({ source, destination }) => {
if (!destination || source?.index === destination?.index) {
return;
}
reorderOptions(source.index, destination.index);
};
const handleOnFxPress = (active, index, key) => {
const _options = options.map((option, i) => {
if (i === index) {
return {
...option,
[key]: {
...option[key],
fxActive: active,
},
};
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
};
const _renderOverlay = (item, index) => {
return (
<Popover className={`${darkMode && 'dark-theme theme-dark'}`} style={{ minWidth: '248px' }}>
<Popover.Body>
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
{'Id'}
</label>
<CodeHinter
type={'basic'}
initialValue={item?.id + ''}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={'Option label'}
onChange={(value) => handleLabelChange('id', value, index)}
/>
</div>
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
{'Label'}
</label>
<CodeHinter
type={'basic'}
initialValue={item?.name}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={'Option label'}
onChange={(value) => handleLabelChange('name', value, index)}
/>
</div>
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
{'Tooltip'}
</label>
<CodeHinter
type={'basic'}
initialValue={item?.tooltip + ''}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={'Tooltip'}
onChange={(value) => handleLabelChange('tooltip', value, index)}
/>
</div>
<div className="field mb-2" data-cy={`input-and-label-column-name`}>
<CodeHinter
initialValue={item?.visible?.value}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
component={component}
type={'fxEditor'}
paramLabel={'Visibility'}
onChange={(value) =>
handleLabelChange(
'visible',
{
value,
},
index
)
}
paramName={'visible'}
onFxPress={(active) => handleOnFxPress(active, index, 'visible')}
fxActive={item?.visible?.fxActive}
fieldMeta={{
type: 'toggle',
displayName: 'Make editable',
}}
paramType={'toggle'}
/>
</div>
<div className="field" data-cy={`input-and-label-column-name`}>
<CodeHinter
initialValue={item?.disabled?.value}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
component={component}
type={'fxEditor'}
paramLabel={'Disable'}
paramName={'disable'}
onChange={(value) => handleLabelChange('disabled', { value }, index)}
onFxPress={(active) => handleOnFxPress(active, index, 'disabled')}
fxActive={item?.disabled?.fxActive}
fieldMeta={{
type: 'toggle',
displayName: 'Make editable',
}}
paramType={'toggle'}
/>
</div>
</Popover.Body>
</Popover>
);
};
const _renderOptions = () => {
return (
<List style={{ marginBottom: '20px' }}>
<DragDropContext
onDragEnd={(result) => {
onDragEnd(result);
}}
>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div className="w-100" {...droppableProps} ref={innerRef}>
{options?.map((item, index) => {
return (
<Draggable key={item.name} draggableId={item.name} index={index}>
{(provided, snapshot) => (
<div
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
>
<OverlayTrigger
trigger="click"
placement="left"
rootClose
overlay={_renderOverlay(item, index)}
>
<div key={item.name + item.id}>
<ListGroup.Item
style={{ marginBottom: '8px', backgroundColor: 'var(--slate3)' }}
onMouseEnter={() => setHoveredOptionIndex(index)}
onMouseLeave={() => setHoveredOptionIndex(null)}
{...restProps}
>
<div className="row">
<div className="col-auto d-flex align-items-center">
<SortableList.DragHandle show />
</div>
<div className="col text-truncate cursor-pointer" style={{ padding: '0px' }}>
{getResolvedValue(item.name)}
</div>
<div className="col-auto">
{index === hoveredOptionIndex && (
<ButtonSolid
variant="danger"
size="xs"
className={'delete-icon-btn'}
onClick={(e) => {
e.stopPropagation();
handleDeleteOption(index);
}}
>
<span className="d-flex">
<Trash fill={'var(--tomato9)'} width={12} />
</span>
</ButtonSolid>
)}
</div>
</div>
</ListGroup.Item>
</div>
</OverlayTrigger>
</div>
)}
</Draggable>
);
})}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<AddNewButton onClick={handleAddOption} dataCy="add-new-dropdown-option" className="mt-0">
Add new option
</AddNewButton>
</List>
);
};
const isDynamicStepsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
useEffect(() => {
setOptions(constructSteps());
}, [component?.id, isDynamicStepsEnabled]);
const constructSteps = () => {
try {
let optionsValue = isDynamicOptionsEnabled
? component?.component?.definition?.properties?.schema?.value
: component?.component?.definition?.properties?.steps?.value;
let options = [];
if (isDynamicOptionsEnabled || typeof optionsValue === 'string') {
options = getResolvedValue(optionsValue);
} else {
options = optionsValue?.map((option) => option);
}
return options.map((option) => {
const newOption = { ...option };
Object.keys(option).forEach((key) => {
if (typeof option[key]?.value === 'boolean') {
newOption[key]['value'] = `{{${option[key]?.value}}}`;
}
});
if (!('visible' in newOption)) {
newOption['visible'] = { value: '{{true}}' };
}
return newOption;
});
} catch (error) {
return [];
}
};
let items = [];
items.push({
title: 'Steps',
isOpen: true,
children: (
<>
{properties
.filter((property) => !optionsProperties.includes(property))
?.map((property) => {
if (property === 'steps') {
return (
<>
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'advanced',
'properties',
currentState,
allComponents
)}
{isDynamicStepsEnabled
? renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'schema',
'properties',
currentState,
allComponents
)
: _renderOptions()}
</>
);
}
// else if (property === 'variant') {
// return renderTest(
// component,
// componentMeta,
// paramUpdated,
// dataQueries,
// 'variant',
// 'properties',
// currentState,
// allComponents,
// handleLabelChange
// );
// }
return renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
property,
'properties',
currentState,
allComponents,
darkMode
);
})}
</>
),
});
items.push({
title: 'Events',
isOpen: true,
children: (
<EventManager
sourceId={component?.id}
eventSourceType="component"
eventMetaDefinition={componentMeta}
dataQueries={dataQueries}
components={allComponents}
eventsChanged={eventsChanged}
apps={apps}
darkMode={darkMode}
pages={pages}
/>
),
});
items.push({
title: `Additional Actions`,
isOpen: true,
children: additionalActions.map((property) => {
return renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
property,
'properties',
currentState,
allComponents,
darkMode,
componentMeta.properties?.[property]?.placeholder
);
}),
});
items.push({
title: 'Devices',
isOpen: true,
children: (
<>
{renderElement(
component,
componentMeta,
layoutPropertyChanged,
dataQueries,
'showOnDesktop',
'others',
currentState,
allComponents
)}
{renderElement(
component,
componentMeta,
layoutPropertyChanged,
dataQueries,
'showOnMobile',
'others',
currentState,
allComponents
)}
</>
),
});
return <Accordion items={items} />;
}
function renderTest(...props) {
const [
component,
componentMeta,
paramUpdated,
dataQueries,
param,
paramType,
currentState,
components = {},
darkMode = false,
placeholder = '',
validationFn,
] = props;
const value = componentMeta?.definition?.properties?.variant?.value;
return (
<div style={{ marginBottom: 8 }}>
<Switch
value={value}
onChange={(e) => {
paramUpdated({ name: 'variant' }, 'value', e, 'properties', false, props);
}}
meta={{
...componentMeta.properties[param],
fullWidth: true,
}}
paramName={param}
isIcon={false}
component={component.component.definition.name}
/>
</div>
);
}

View file

@ -255,7 +255,7 @@ export const PropertiesTabElements = ({
paramType="properties"
/>
</div>
{resolveReferences(column?.isEditable) && (
{(column?.fxActiveFields?.includes('isEditable') || resolveReferences(column?.isEditable)) && (
<ValidationProperties
column={column}
index={index}

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect, useContext } from 'react';
import React, { useState, useEffect, useContext, useRef } from 'react';
import { ActionTypes } from '@/Editor/ActionTypes';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
@ -32,6 +32,9 @@ import useStore from '@/AppBuilder/_stores/store';
import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice';
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { components as selectComponents } from 'react-select';
export const EventManager = ({
sourceId,
@ -82,6 +85,8 @@ export const EventManager = ({
const [events, setEvents] = useState([]);
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
const lastFocusedEventIndex = useRef(null);
const shouldSkipOnToggle = useRef(null);
const { t } = useTranslation();
@ -101,8 +106,23 @@ export const EventManager = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(currentEvents)]);
let actionOptions = ActionTypes.map((action) => {
return { name: action.name, value: action.id };
let groupedOptions = ActionTypes.reduce((acc, action) => {
const groupName = action.group;
if (!acc[groupName]) {
acc[groupName] = [];
}
acc[groupName].push({
label: action.name,
value: action.id,
});
return acc;
}, {});
let actionOptions = Object.keys(groupedOptions).map((groupName) => {
return { label: groupName, options: groupedOptions[groupName] };
});
let checkIfClicksAreInsideOf = document.querySelector('.cm-completionListIncompleteBottom');
@ -124,6 +144,46 @@ export const EventManager = ({
}),
};
const actionStyles = {
...styles,
menuList: (base) => ({
...base,
padding: '8px 0 8px 8px',
'&::-webkit-scrollbar': {
width: '10px',
},
'&::-webkit-scrollbar-track': {
background: 'transparent',
},
'&::-webkit-scrollbar-thumb': {
background: '#E4E7EB',
border: '1px solid transparent',
backgroundClip: 'content-box',
},
'&::-webkit-scrollbar-thumb:hover': {
background: '#E4E7EB !important',
border: '1px solid transparent !important',
backgroundClip: 'content-box !important',
},
'&:hover': {
'&::-webkit-scrollbar-thumb': {
background: '#E4E7EB !important',
border: '1px solid transparent !important',
backgroundClip: 'content-box !important',
},
},
}),
group: (base) => ({
...base,
padding: 0,
}),
groupHeading: (base) => ({
...base,
margin: 0,
padding: '0',
}),
};
const actionLookup = Object.fromEntries(ActionTypes.map((actionType) => [actionType.id, actionType]));
let alertTypes = [
@ -394,6 +454,29 @@ export const EventManager = ({
return defaultValue;
};
const formatGroupLabel = (data) => {
if (data.label === 'run-action') return;
return (
<div
className="tw-border-x-0 tw-border-t-0 tw-border-b-[0.5px] tw-border-solid tw-my-[4px]"
style={{ borderColor: 'var(--border-weak)' }}
></div>
);
};
const CustomOption = (props) => {
return (
<selectComponents.Option {...props}>
<div className="d-flex align-items-center">
<div style={{ width: '16px', marginRight: '6px' }}>
{props.isSelected && <SolidIcon name="tickv3" width="16px" height="16px" />}
</div>
<span>{props.label}</span>
</div>
</selectComponents.Option>
);
};
function eventPopover(event, index) {
return (
<Popover
@ -433,13 +516,17 @@ export const EventManager = ({
<Select
className={`${darkMode ? 'select-search-dark' : 'select-search'} w-100`}
options={actionOptions}
value={event.actionId}
value={actionOptions
.flatMap((group) => group.options)
.find((option) => option.value === event.actionId)}
components={{ Option: CustomOption }}
search={false}
onChange={(value) => handlerChanged(index, 'actionId', value)}
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
styles={actionStyles}
useMenuPortal={false}
useCustomStyles={true}
formatGroupLabel={formatGroupLabel}
/>
</div>
</div>
@ -1006,10 +1093,21 @@ export const EventManager = ({
placement={popoverPlacement || 'left'}
rootClose={true}
overlay={eventPopover(event.event, index)}
onHide={() => setFocusedEventIndex(null)}
onToggle={(showing) => {
// If the toggle action should be skipped (e.g., due to a previous state change), reset the flag and exit early.
if (shouldSkipOnToggle.current) {
shouldSkipOnToggle.current = false;
return;
}
// If there is already a focused event, set the skip flag to prevent unnecessary state updates.
if (focusedEventIndex !== null && showing) {
shouldSkipOnToggle.current = true;
}
if (showing) {
setFocusedEventIndex(index);
lastFocusedEventIndex.current = index;
} else {
setFocusedEventIndex(null);
}
@ -1018,6 +1116,7 @@ export const EventManager = ({
>
<div
key={index}
id={`${sourceId}-${index}`}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
@ -1061,6 +1160,17 @@ export const EventManager = ({
);
};
const shouldUsePopoverObserver = events.length !== 0 && eventSourceType === 'data_query';
usePopoverObserver(
shouldUsePopoverObserver ? document.getElementsByClassName('query-details')[0] : null,
document.getElementById(`${sourceId}-${lastFocusedEventIndex.current}`),
document.getElementById('popover-basic'),
focusedEventIndex !== null,
() => (document.getElementById('popover-basic').style.display = 'block'),
() => (document.getElementById('popover-basic').style.display = 'none')
);
if (events.length === 0) {
return (
<>

View file

@ -36,6 +36,7 @@ import Inspect from '@/_ui/Icon/solidIcons/Inspect';
import classNames from 'classnames';
import { EMPTY_ARRAY } from '@/_stores/editorStore';
import { Select } from './Components/Select';
import { Steps } from './Components/Steps.jsx';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import useStore from '@/AppBuilder/_stores/store';
// import { componentTypes } from '@/Editor/WidgetManager/components';
@ -90,6 +91,7 @@ const NEW_REVAMPED_COMPONENTS = [
'VerticalDivider',
'ModalV2',
'Link',
'Steps',
];
export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selectedComponentId }) => {
@ -539,8 +541,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
componentMeta.displayName === 'Toggle Switch (Legacy)'
? 'Toggle (Legacy)'
: componentMeta.displayName === 'Toggle Switch'
? 'Toggle Switch'
: componentMeta.component,
? 'Toggle Switch'
: componentMeta.component,
})}
</small>
</span>
@ -740,6 +742,8 @@ const GetAccordion = React.memo(
case 'DatePickerV2':
case 'TimePicker':
return <DatetimePickerV2 {...restProps} componentName={componentName} />;
case 'Steps':
return <Steps {...restProps} />;
case 'PhoneInput':
return <PhoneInput {...restProps} />;
case 'CurrencyInput':

View file

@ -14,6 +14,9 @@ const NEW_WIDGETS = [
'TimePicker',
'ModalV2',
'TextArea',
'EmailInput',
'PhoneInput',
'CurrencyInput',
];
export const WidgetBox = ({ component, darkMode }) => {

View file

@ -168,6 +168,7 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
showViewerNavigation={!isPagesSidebarHidden}
handleAppEnvironmentChanged={handleAppEnvironmentChanged}
changeToDarkMode={changeToDarkMode}
switchPage={switchPage}
/>
)}
<div className="sub-section">

View file

@ -3,8 +3,8 @@ export const containerConfig = {
displayName: 'Container',
description: 'Group components',
defaultSize: {
width: 10,
height: 200,
width: 13,
height: 480,
},
component: 'Container',
others: {

View file

@ -4,25 +4,38 @@ export const stepsConfig = {
description: 'Step-by-step navigation aid',
component: 'Steps',
properties: {
variant: {
type: 'switch',
displayName: 'Variant',
validation: { schema: { type: 'string' }, defaultValue: 'titles' },
options: [
{ displayName: 'Label', value: 'titles' },
{ displayName: 'Number', value: 'numbers' },
{ displayName: 'Plain', value: 'plain' },
],
accordian: 'label',
},
schema: {
type: 'code',
displayName: 'Schema',
conditionallyRender: {
key: 'advanced',
value: true,
},
accordian: 'Options',
},
steps: {
type: 'code',
displayName: 'Steps',
displayName: '',
showLabel: false,
validation: {
schema: {
type: 'array',
element: { type: 'object', object: { id: { type: 'number' } } },
element: { type: 'object' },
},
defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`,
},
},
currentStep: {
type: 'code',
displayName: 'Current step',
validation: {
schema: { type: 'number' },
defaultValue: 1,
},
},
stepsSelectable: {
type: 'toggle',
displayName: 'Steps selectable',
@ -30,7 +43,38 @@ export const stepsConfig = {
schema: { type: 'boolean' },
defaultValue: false,
},
section: 'additionalActions',
},
disabledState: {
type: 'toggle',
displayName: 'Disable',
validation: { schema: { type: 'boolean' } },
section: 'additionalActions',
},
visibility: {
type: 'toggle',
displayName: 'Visibility',
validation: { schema: { type: 'boolean' }, defaultValue: true },
section: 'additionalActions',
},
advanced: {
type: 'toggle',
displayName: 'Dynamic options',
validation: {
schema: { type: 'boolean' },
defaultValue: true,
},
accordian: 'Options',
},
currentStep: {
type: 'code',
displayName: 'Current step',
validation: {
schema: { type: 'number' },
defaultValue: 1,
},
},
},
defaultSize: {
width: 22,
@ -40,46 +84,126 @@ export const stepsConfig = {
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
},
actions: [
{
handle: 'setStep',
displayName: 'Set step',
params: [
{
handle: 'option',
displayName: 'Option',
},
],
},
{
handle: 'setVisibility',
displayName: 'Set visibility',
params: [{ handle: 'visible', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
},
{
handle: 'setDisabled',
displayName: 'Set disabled',
params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{true}}', type: 'toggle' }],
},
{
handle: 'resetSteps',
displayName: 'Reset steps',
params: [],
},
{
handle: 'setStepVisible',
displayName: 'Set step visible',
params: [
{
handle: 'id',
displayName: 'Step id',
},
{
handle: 'visibility',
displayName: 'visibility',
defaultValue: '{{false}}',
type: 'toggle',
},
],
},
{
handle: 'setStepDisable',
displayName: 'Set step disable',
params: [
{
handle: 'id',
displayName: 'Step id',
},
{
handle: 'disabled',
displayName: 'disabled',
defaultValue: '{{true}}',
type: 'toggle',
},
],
},
],
events: {
onSelect: { displayName: 'On select' },
},
styles: {
color: {
incompletedAccent: {
type: 'colorSwatches',
displayName: 'colorSwatches',
displayName: 'Incompleted accent',
validation: {
schema: { type: 'string' },
defaultValue: '#CCD1D5',
},
accordian: 'steps',
},
incompletedLabel: {
type: 'colorSwatches',
displayName: 'Incompleted label',
validation: {
schema: { type: 'string' },
defaultValue: '#1B1F24',
},
accordian: 'steps',
},
completedAccent: {
type: 'colorSwatches',
displayName: 'Completed accent',
validation: {
schema: { type: 'string' },
defaultValue: 'var(--primary-brand)',
},
accordian: 'steps',
},
textColor: {
completedLabel: {
type: 'colorSwatches',
displayName: 'Text color',
displayName: 'Completed label',
validation: {
schema: { type: 'string' },
defaultValue: '#000000',
defaultValue: '#1B1F24',
},
accordian: 'steps',
},
theme: {
type: 'select',
displayName: 'Theme',
currentStepLabel: {
type: 'colorSwatches',
displayName: 'Current step label',
validation: {
schema: { type: 'string' },
defaultValue: '#1B1F24',
},
accordian: 'steps',
},
padding: {
type: 'switch',
displayName: 'Padding',
validation: {
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
defaultValue: 'default',
},
options: [
{ name: 'titles', value: 'titles' },
{ name: 'numbers', value: 'numbers' },
{ name: 'plain', value: 'plain' },
{ displayName: 'Default', value: 'default' },
{ displayName: 'None', value: 'none' },
],
validation: {
schema: { type: 'string' },
defaultValue: 'titles',
},
},
visibility: {
type: 'toggle',
displayName: 'Visibility',
validation: {
schema: { type: 'boolean' },
defaultValue: true,
},
accordian: 'container',
},
},
exposedVariables: {
@ -92,17 +216,35 @@ export const stepsConfig = {
},
properties: {
steps: {
value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`,
value: [
{ name: 'step 1', tooltip: '', id: 1, visible: { value: true }, disabled: { value: false } },
{ name: 'step 2', tooltip: '', id: 2, visible: { value: true }, disabled: { value: false } },
{ name: 'step 3', tooltip: '', id: 3, visible: { value: true }, disabled: { value: false } },
{ name: 'step 4', tooltip: '', id: 4, visible: { value: true }, disabled: { value: false } },
{ name: 'step 5', tooltip: '', id: 5, visible: { value: true }, disabled: { value: false } },
],
},
schema: {
value: `{{ [{ name: 'step 1', tooltip: '', id: 1,visible: true, disabled: false},{ name: 'step 2', tooltip: '', id: 2,visible: true, disabled: false},{ name: 'step 3', tooltip: '', id: 3,visible: true, disabled: false},{ name: 'step 4', tooltip: '', id: 4,visible: true, disabled: false},{ name: 'step 5', tooltip: '', id: 5,visible: true, disabled: false}]}}`,
},
disabledState: { value: '{{false}}' },
variant: { value: 'titles' },
currentStep: { value: '{{3}}' },
stepsSelectable: { value: true },
advanced: { value: `{{false}}` },
visibility: { value: '{{true}}' },
},
events: [],
styles: {
visibility: { value: '{{true}}' },
theme: { value: 'titles' },
color: { value: 'var(--primary-brand)' },
textColor: { value: '' },
// color: { value: '' },
// textColor: { value: '' },
padding: { value: 'default' },
incompletedAccent: { value: '#E4E7EB' },
incompletedLabel: { value: '#1B1F24' },
completedAccent: { value: 'var(--primary-brand)' },
completedLabel: { value: '#1B1F24' },
currentStepLabel: { value: '#1B1F24' },
},
},
};

View file

@ -216,248 +216,237 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
}
// const appDataPromise = appService.fetchApp(appId);
appDataPromise
.then(async (result) => {
let appData = { ...result };
let editorEnvironment = result.editorEnvironment;
if (isPreviewForVersion) {
const rawDataQueries = appData?.data_queries;
const rawEditingVersionDataQueries = appData?.editing_version?.data_queries;
appData = convertAllKeysToSnakeCase(appData);
appDataPromise.then(async (result) => {
let appData = { ...result };
let editorEnvironment = result.editorEnvironment;
if (isPreviewForVersion) {
const rawDataQueries = appData?.data_queries;
const rawEditingVersionDataQueries = appData?.editing_version?.data_queries;
appData = convertAllKeysToSnakeCase(appData);
appData.data_queries = rawDataQueries;
if (appData.editing_version && rawEditingVersionDataQueries) {
appData.editing_version.data_queries = rawEditingVersionDataQueries;
}
appData.data_queries = rawDataQueries;
if (appData.editing_version && rawEditingVersionDataQueries) {
appData.editing_version.data_queries = rawEditingVersionDataQueries;
}
editorEnvironment = {
id: environmentId,
name: queryParams.env,
};
}
let constantsResp;
if (mode !== 'edit') {
try {
const queryParams = { slug: slug };
const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams);
editorEnvironment = {
id: environmentId,
name: queryParams.env,
id: viewerEnvironment?.environment?.id,
name: viewerEnvironment?.environment?.name,
};
constantsResp =
isPublicAccess && appData.is_public
? await orgEnvironmentConstantService.getConstantsFromPublicApp(slug, viewerEnvironment?.environment?.id)
: await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id);
} catch (error) {
console.error('Error fetching viewer environment:', error);
}
}
let constantsResp;
if (mode !== 'edit') {
try {
const queryParams = { slug: slug };
const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams);
editorEnvironment = {
id: viewerEnvironment?.environment?.id,
name: viewerEnvironment?.environment?.name,
};
constantsResp =
isPublicAccess && appData.is_public
? await orgEnvironmentConstantService.getConstantsFromPublicApp(
slug,
viewerEnvironment?.environment?.id
)
: await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id);
} catch (error) {
console.error('Error fetching viewer environment:', error);
}
}
if (mode === 'edit') {
constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id);
}
// get the constants for specific environment
constantsResp.constants = extractEnvironmentConstantsFromConstantsList(
constantsResp?.constants,
editorEnvironment?.name
);
if (mode === 'edit') {
constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id);
}
// get the constants for specific environment
constantsResp.constants = extractEnvironmentConstantsFromConstantsList(
constantsResp?.constants,
editorEnvironment?.name
);
setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public);
setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public);
fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public);
fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public);
const pages = appData.pages.map((page) => {
return page;
});
const conversation = appData.ai_conversation;
const docsConversation = appData.ai_conversation_learn;
if (setConversation && setDocsConversation) {
setConversation(conversation);
setDocsConversation(docsConversation);
// important to control ai inputs
getCreditBalance();
}
let showWalkthrough = true;
// if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message
// handles the getappdataby slug api call. Gets the homePageId from the appData.
const homePageId =
appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id;
setApp({
appName: appData.name,
appId: appData.id,
slug: appData.slug,
currentAppEnvironmentId: editorEnvironment.id,
isMaintenanceOn:
'is_maintenance_on' in result
? result.is_maintenance_on
: 'isMaintenanceOn' in result
? result.isMaintenanceOn
: false,
organizationId: appData.organizationId || appData.organization_id,
homePageId: homePageId,
isPublic: appData.is_public,
creationMode: appData.creation_mode,
});
setIsEditorFreezed(appData.should_freeze_editor);
const global_settings = mapKeys(
appData.editing_version?.global_settings || appData.global_settings,
(value, key) => camelCase(key)
);
if (!global_settings?.theme) {
global_settings.theme = baseTheme;
}
setGlobalSettings(global_settings);
setPages(pages, moduleId);
setPageSettings(
computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings))
);
// set starting page as homepage initially
let startingPage = appData.pages.find((page) => page.id === homePageId);
//no access to homepage, set to the next available page
if (startingPage?.restricted) {
startingPage = appData.pages.find((page) => !page?.restricted);
}
if (initialLoadRef.current) {
// if initial load, check if the path has a page handle and set that as the starting page
const initialLoadPath = location.pathname.split('/').pop();
const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup);
if (page) {
// if page is disabled, and not editing redirect to home page
const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled);
if (shouldRedirect) {
const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle);
window.history.replaceState(null, null, newUrl);
if (page?.restricted) {
toast.error('Access to this page is restricted. Contact admin to know more.', {
className: 'text-nowrap w-auto mw-100',
});
}
} else {
startingPage = page;
}
}
// navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
}
// Add page id and handle to the state on initial load
const currentState = window.history.state || {};
const pageInfo = {
id: startingPage.id,
handle: startingPage.handle,
};
const newState = { ...currentState, ...pageInfo };
window.history.replaceState(newState, '', window.location.href);
setCurrentPageHandle(startingPage.handle);
updateFeatureAccess();
setCurrentPageId(startingPage.id, moduleId);
setResolvedPageConstants({
id: startingPage?.id,
handle: startingPage?.handle,
name: startingPage?.name,
});
setComponentNameIdMapping(moduleId);
updateEventsField('events', appData.events);
setCurrentVersionId(appData.editing_version?.id || appData.current_version_id);
setAppHomePageId(homePageId);
const queryData =
isPublicAccess || (mode !== 'edit' && appData.is_public)
? appData
: await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id);
const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries;
dataQueries.forEach((query) => normalizeQueryTransformationOptions(query));
setQueries(dataQueries);
if (dataQueries?.length > 0) {
setSelectedQuery(dataQueries[0]?.id);
initialiseResolvedQuery(dataQueries.map((query) => query.id));
}
const constants = constantsResp?.constants;
if (constants) {
const orgConstants = {};
const orgSecrets = {};
constants.map((constant) => {
if (constant.type !== 'Secret') {
orgConstants[constant.name] = constant.value;
} else {
orgSecrets[constant.name] = constant.value;
}
});
setResolvedConstants(orgConstants);
setSecrets(orgSecrets);
}
setQueryMapping(moduleId);
setResolvedGlobals('environment', editorEnvironment);
setResolvedGlobals('mode', { value: mode });
setResolvedGlobals('currentUser', {
...user,
groups: currentSession?.groups,
role: currentSession?.role?.name,
ssoUserInfo: currentSession?.ssoUserInfo,
...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata)
? { metadata: currentSession?.currentUser?.metadata }
: {}),
});
setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))));
initDependencyGraph(moduleId);
setCurrentMode(mode); // TODO: set mode based on the slug/appDef
if (
state.ai &&
state?.prompt &&
initialLoadRef.current &&
(conversation?.aiConversationMessages || []).length === 0
) {
setSelectedSidebarItem('tooljetai');
toggleLeftSidebar('true');
sendMessage(state.prompt);
setConversationZeroState(true);
showWalkthrough = false;
}
// fetchDataSources(appData.editing_version.id, editorEnvironment.id);
if (!isPublicAccess) {
const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env');
useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams);
fetchGlobalDataSources(
appData.organization_id,
appData.editing_version?.id || appData.current_version_id,
editorEnvironment.id
);
}
useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed
updateReleasedVersionId(appData.current_version_id);
setEditorLoading(false);
initialLoadRef.current = false;
// only show if app is not created from prompt
if (showWalkthrough) initEditorWalkThrough();
checkAndSetTrueBuildSuggestionsFlag();
return () => {
document.title = retrieveWhiteLabelText();
};
})
.catch((error) => {
if (isPublicAccess) {
if (mode !== 'edit') {
handleError('view', error);
}
}
const pages = appData.pages.map((page) => {
return page;
});
const conversation = appData.ai_conversation;
const docsConversation = appData.ai_conversation_learn;
if (setConversation && setDocsConversation) {
setConversation(conversation);
setDocsConversation(docsConversation);
// important to control ai inputs
getCreditBalance();
}
let showWalkthrough = true;
// if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message
// handles the getappdataby slug api call. Gets the homePageId from the appData.
const homePageId =
appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id;
setApp({
appName: appData.name,
appId: appData.id,
slug: appData.slug,
currentAppEnvironmentId: editorEnvironment.id,
isMaintenanceOn:
'is_maintenance_on' in result
? result.is_maintenance_on
: 'isMaintenanceOn' in result
? result.isMaintenanceOn
: false,
organizationId: appData.organizationId || appData.organization_id,
homePageId: homePageId,
isPublic: appData.is_public,
creationMode: appData.creation_mode,
});
setIsEditorFreezed(appData.should_freeze_editor);
const global_settings = mapKeys(
appData.editing_version?.global_settings || appData.global_settings,
(value, key) => camelCase(key)
);
if (!global_settings?.theme) {
global_settings.theme = baseTheme;
}
setGlobalSettings(global_settings);
setPages(pages, moduleId);
setPageSettings(
computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings))
);
// set starting page as homepage initially
let startingPage = appData.pages.find((page) => page.id === homePageId);
//no access to homepage, set to the next available page
if (startingPage?.restricted) {
startingPage = appData.pages.find((page) => !page?.restricted);
}
if (initialLoadRef.current) {
// if initial load, check if the path has a page handle and set that as the starting page
const initialLoadPath = location.pathname.split('/').pop();
const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup);
if (page) {
// if page is disabled, and not editing redirect to home page
const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled);
if (shouldRedirect) {
const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle);
window.history.replaceState(null, null, newUrl);
if (page?.restricted) {
toast.error('Access to this page is restricted. Contact admin to know more.', {
className: 'text-nowrap w-auto mw-100',
});
}
} else {
startingPage = page;
}
}
// navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
}
// Add page id and handle to the state on initial load
const currentState = window.history.state || {};
const pageInfo = {
id: startingPage.id,
handle: startingPage.handle,
};
const newState = { ...currentState, ...pageInfo };
window.history.replaceState(newState, '', window.location.href);
setCurrentPageHandle(startingPage.handle);
updateFeatureAccess();
setCurrentPageId(startingPage.id, moduleId);
setResolvedPageConstants({
id: startingPage?.id,
handle: startingPage?.handle,
name: startingPage?.name,
});
setComponentNameIdMapping(moduleId);
updateEventsField('events', appData.events);
setCurrentVersionId(appData.editing_version?.id || appData.current_version_id);
setAppHomePageId(homePageId);
const queryData =
isPublicAccess || (mode !== 'edit' && appData.is_public)
? appData
: await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id);
const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries;
dataQueries.forEach((query) => normalizeQueryTransformationOptions(query));
setQueries(dataQueries);
if (dataQueries?.length > 0) {
setSelectedQuery(dataQueries[0]?.id);
initialiseResolvedQuery(dataQueries.map((query) => query.id));
}
const constants = constantsResp?.constants;
if (constants) {
const orgConstants = {};
const orgSecrets = {};
constants.map((constant) => {
if (constant.type !== 'Secret') {
orgConstants[constant.name] = constant.value;
} else {
orgSecrets[constant.name] = constant.value;
}
});
setResolvedConstants(orgConstants);
setSecrets(orgSecrets);
}
setQueryMapping(moduleId);
setResolvedGlobals('environment', editorEnvironment);
setResolvedGlobals('mode', { value: mode });
setResolvedGlobals('currentUser', {
...user,
groups: currentSession?.groups,
role: currentSession?.role?.name,
ssoUserInfo: currentSession?.ssoUserInfo,
...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata)
? { metadata: currentSession?.currentUser?.metadata }
: {}),
});
setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))));
initDependencyGraph(moduleId);
setCurrentMode(mode); // TODO: set mode based on the slug/appDef
if (
state.ai &&
state?.prompt &&
initialLoadRef.current &&
(conversation?.aiConversationMessages || []).length === 0
) {
setSelectedSidebarItem('tooljetai');
toggleLeftSidebar('true');
sendMessage(state.prompt);
setConversationZeroState(true);
showWalkthrough = false;
}
// fetchDataSources(appData.editing_version.id, editorEnvironment.id);
if (!isPublicAccess) {
const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env');
useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams);
fetchGlobalDataSources(
appData.organization_id,
appData.editing_version?.id || appData.current_version_id,
editorEnvironment.id
);
}
useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed
updateReleasedVersionId(appData.current_version_id);
setEditorLoading(false);
initialLoadRef.current = false;
// only show if app is not created from prompt
if (showWalkthrough) initEditorWalkThrough();
checkAndSetTrueBuildSuggestionsFlag();
return () => {
document.title = retrieveWhiteLabelText();
};
});
}, [setApp, setEditorLoading, currentSession]);
useEffect(() => {

Some files were not shown because too many files have changed in this diff Show more