Merge branch 'main' into feat/steps-v2-alignment-style-improvement

This commit is contained in:
johnsoncherian 2025-04-29 13:00:33 +05:30
commit 68bf2807fd
255 changed files with 3183 additions and 18235 deletions

View file

@ -3,7 +3,6 @@ name: Cypress App-Builder
on:
pull_request_target:
types: [labeled, unlabeled, closed]
workflow_dispatch:
env:
@ -54,20 +53,9 @@ jobs:
git submodule foreach --recursive '
git checkout ${{ env.BRANCH_NAME }} 2>/dev/null || git checkout main'
- name: Set up Docker
uses: docker-practice/actions-setup-docker@master
- name: Run PosgtreSQL Database Docker Container
run: |
sudo docker network create tooljet
sudo docker run -d --name postgres --network tooljet -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_PORT=5432 -d postgres:13
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Install and build dependencies
run: |
npm cache clean --force
@ -76,50 +64,59 @@ jobs:
npm install --prefix frontend
npm run build:plugins
- name: Local development setup
run: |
sudo docker network create tooljet
sudo docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_PORT=5432 -d postgres:13
- name: Run PostgREST Docker Container
run: |
sudo docker run -d --name postgrest --network tooljet -p 3001:3000 \
-e PGRST_DB_URI="postgres://postgres:postgres@localhost:5432/tooljet" \
-e PGRST_DB_ANON_ROLE="postgres" \
-e PGRST_JWT_SECRET="r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" \
-e PGRST_DB_PRE_CONFIG=postgrest.pre_config \
postgrest/postgrest:v12.2.0
- name: Set up environment variables
run: |
echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'EE' || 'CE' }}" >> .env
echo "TOOLJET_EDITION=${{ matrix.edition == 'ee' && 'ee' || 'ce' }}" >> .env
echo "TOOLJET_HOST=http://localhost:8082" >> .env
echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env
echo "PG_DB=tooljet_development" >> .env
echo "PG_USER=postgres" >> .env
echo "PG_HOST=localhost" >> .env
echo "PG_PASS=postgres" >> .env
echo "PG_PORT=5432" >> .env
echo "ENABLE_TOOLJET_DB=true" >> .env
echo "TOOLJET_DB=tooljet" >> .env
echo "TOOLJET_DB=tooljet_db" >> .env
echo "TOOLJET_DB_USER=postgres" >> .env
echo "TOOLJET_DB_HOST=localhost" >> .env
echo "TOOLJET_DB_PASS=postgres" >> .env
echo "PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" >> .env
echo "PGRST_HOST=localhost:3001" >> .env
echo "TOOLJET_DB_STATEMENT_TIMEOUT=60000" >> .env
echo "TOOLJET_DB_RECONFIG=true" >> .env
echo "PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" >> .env
echo "PGRST_HOST=localhost:3001" >> .env
echo "PGRST_DB_PRE_CONFIG=postgrest.pre_config" >> .env
echo "PGRST_DB_URI=postgres://postgres:postgres@localhost:5432/tooljet" >> .env
echo "ENABLE_MARKETPLACE_FEATURE=true" >> .env
echo "ENABLE_MARKETPLACE_DEV_MODE=true" >> .env
echo "ENABLE_PRIVATE_APP_EMBED=true" >> .env
- name: Set up database
run: |
npm run --prefix server db:create
npm run --prefix server db:reset
npm run --prefix server db:seed
- name: sleep 5
run: sleep 5
- name: Run PostgREST Docker Container
- name: Start services
run: |
sudo docker run -d --name postgrest --network tooljet -p 3001:3000 \
-e PGRST_DB_URI="postgres://postgres:postgres@postgres:5432/tooljet" -e PGRST_DB_ANON_ROLE="postgres" -e PGRST_JWT_SECRET="r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj" -e PGRST_DB_PRE_CONFIG=postgrest.pre_config \
postgrest/postgrest:v12.2.0
- name: Run plugins compilation in watch mode
run: cd plugins && npm start &
- name: Run the server
run: cd server && npm run start:dev &
- name: Run the client
run: cd frontend && npm start &
cd plugins && npm start &
cd server && npm run start:dev &
cd frontend && npm start &
- name: Wait for the server to be ready
run: |
@ -128,6 +125,18 @@ jobs:
sleep 5
done'
- name: Seeding (Setup Super Admin)
run: |
curl 'http://localhost:3000/api/onboarding/setup-super-admin' \
-H 'Content-Type: application/json' \
--data-raw '{
"companyName": "ToolJet",
"name": "The Developer",
"workspaceName": "Tooljet'\''s workspace",
"email": "dev@tooljet.io",
"password": "password"
}'
- name: docker logs
run: sudo docker logs postgrest
@ -140,7 +149,7 @@ jobs:
dir: "./cypress-tests"
- name: App builder
uses: cypress-io/github-action@v5
uses: cypress-io/github-action@v6
with:
working-directory: ./cypress-tests
config: "baseUrl=http://localhost:8082"
@ -150,10 +159,10 @@ jobs:
uses: actions/upload-artifact@v4
if: always()
with:
name: screenshots
name: screenshots-appbuilder-${{ matrix.edition }}
path: cypress-tests/cypress/screenshots
Cypress-App-builder-Subpath:
Cypress-App-builder-Subpath:
runs-on: ubuntu-22.04
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'run-cypress-app-builder-subpath' }}
@ -228,8 +237,17 @@ jobs:
sleep 5
done'
- name: Seeding
run: docker exec Tooljet-app npm run db:seed:prod
- name: Seeding (Setup Super Admin)
run: |
curl 'http://localhost:3000/api/onboarding/setup-super-admin' \
-H 'Content-Type: application/json' \
--data-raw '{
"companyName": "ToolJet",,
"name": "The Developer",
"workspaceName": "Tooljet'\''s workspace",
"email": "dev@tooljet.io",
"password": "password"
}'
- name: Create Cypress environment file
id: create-json

View file

@ -67,8 +67,8 @@ jobs:
with:
context: .
file: docker/ce-production.Dockerfile
push: false
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}
push: true
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
@ -80,8 +80,8 @@ jobs:
with:
context: .
file: docker/ee/ee-production.Dockerfile
push: false
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}
push: true
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
@ -116,10 +116,16 @@ jobs:
- name: Pulling the docker-compose file
run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data
- name: Update docker-compose file
- name: Update docker-compose file for CE
run: |
# Update docker-compose.yaml with the new image
sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}|' docker-compose.yaml
sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ce|' docker-compose.yaml
- name: Update docker-compose file for CE
if: matrix.edition == 'ee'
run: |
# Update docker-compose.yaml with the new image
sed -i '/^[[:space:]]*tooljet:/,/^$/ s|^\([[:space:]]*image:[[:space:]]*\).*|\1tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}-ee|' docker-compose.yaml
- name: Install Docker Compose
run: |
@ -132,6 +138,9 @@ jobs:
- name: Checking containers
run: docker ps -a
- name: Checking containers
run: docker ps -a
- name: docker logs
run: sudo docker logs Tooljet-app
@ -142,8 +151,18 @@ jobs:
sleep 5
done'
- name: Seeding
run: docker exec Tooljet-app npm run db:seed:prod
- name: Seeding (Setup Super Admin)
run: |
curl 'http://localhost:3000/api/onboarding/setup-super-admin' \
-H 'Content-Type: application/json' \
--data-raw '{
"companyName": "ToolJet",
"name": "The Developer",
"workspaceName": "Tooljet'\''s workspace",
"email": "dev@tooljet.io",
"password": "password"
}'
- name: Create Cypress environment file
id: create-json

View file

@ -102,6 +102,10 @@ jobs:
echo "ENABLE_MARKETPLACE_FEATURE=true" >> .env
echo "ENABLE_MARKETPLACE_DEV_MODE=true" >> .env
echo "ENABLE_PRIVATE_APP_EMBED=true" >> .env
echo "SSO_GOOGLE_OAUTH2_CLIENT_ID=123456789.apps.googleusercontent.com" >> .env
echo "SSO_GOOGLE_OAUTH2_CLIENT_SECRET=ABCGFDNF-FHSDVFY-bskfh6234" >> .env
echo "SSO_GIT_OAUTH2_CLIENT_ID=1234567890" >> .env
echo "SSO_GIT_OAUTH2_CLIENT_SECRET=3346shfvkdjjsfkvxce32854e026a4531ed" >> .env
- name: Set up database
run: |

View file

@ -19,9 +19,9 @@ module.exports = defineConfig({
trashAssetsBeforeRuns: true,
e2e: {
setupNodeEvents (on, config) {
setupNodeEvents(on, config) {
on("task", {
readPdf (pathToPdf) {
readPdf(pathToPdf) {
return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf);
let dataBuffer = fs.readFileSync(pdfPath);
@ -33,7 +33,7 @@ module.exports = defineConfig({
});
on("task", {
readXlsx (filePath) {
readXlsx(filePath) {
return new Promise((resolve, reject) => {
try {
let dataBuffer = fs.readFileSync(filePath);
@ -48,7 +48,7 @@ module.exports = defineConfig({
});
on("task", {
deleteFolder (folderName) {
deleteFolder(folderName) {
return new Promise((resolve, reject) => {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) {
@ -62,7 +62,7 @@ module.exports = defineConfig({
});
on("task", {
dbConnection ({ dbconfig, sql }) {
dbConnection({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig);
return client.query(sql);
},
@ -76,8 +76,8 @@ module.exports = defineConfig({
experimentalRunAllSpecs: true,
baseUrl: "http://localhost:8082",
specPattern: [
"cypress/e2e/happyPath/appbuilder/commonTestcases/**/*.cy.js",
"cypress/e2e/happyPath/appbuilder/ceTestcases/**/*.cy.js"
"cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/**/*.cy.js",
// "cypress/e2e/happyPath/appbuilder/ceTestcases/**/*.cy.js"
],
numTestsKeptInMemory: 1,
redirectionLimit: 7,

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);
},
@ -98,6 +98,7 @@ module.exports = defineConfig({
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/commonTestcases/**/*.cy.js",
],

View file

@ -92,11 +92,7 @@ module.exports = defineConfig({
experimentalModfyObstructiveThirdPartyCode: true,
experimentalRunAllSpecs: true,
baseUrl: "http://localhost:8082",
specPattern: [
"cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/!(userFlow)/**/*.cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js",
],
specPattern: "cypress/e2e/happyPath/**/*.cy.js",
downloadsFolder: "cypress/downloads",
numTestsKeptInMemory: 0,
redirectionLimit: 10,

View file

@ -52,7 +52,7 @@ Cypress.Commands.add("apiCreateGDS", (url, name, kind, options) => {
log: false;
}
expect(response.status).to.equal(201);
Cypress.env(`${name}-id`, response.body.id);
Cypress.env(`${kind}`, response.body.id);
Cypress.log({
name: "Create Data Source",
@ -63,6 +63,30 @@ Cypress.Commands.add("apiCreateGDS", (url, name, kind, options) => {
});
});
Cypress.Commands.add("apiFetchDataSourcesId", () => {
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/data-sources/${Cypress.env("workspaceId")}/environments/${Cypress.env("environmentId")}/versions/${Cypress.env("editingVersionId")}`,
headers,
}).then((response) => {
expect(response.status).to.equal(200);
const dataSources = response.body?.data_sources || [];
dataSources.forEach((item) => {
Cypress.env(`${item.kind}`, `${item.id}`);
});
Cypress.log({
name: "DS Fetch",
displayName: "Data Sources Fetched",
message: dataSources.map(ds => `\nKind: '${ds.kind}', Name: '${ds.id}'`).join(','),
});
});
});
});
Cypress.Commands.add("apiCreateApp", (appName = "testApp") => {
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("walkthroughCompleted", "true");
@ -140,14 +164,11 @@ Cypress.Commands.add(
cy.visit(`/${workspaceId}/apps/${appId}/${slug}`);
cy.wait("@getAppData").then((interception) => {
// Assuming the response body is a JSON object
const responseData = interception.response.body;
// Set the response data as an environment variable
Cypress.env("apiResponseData", responseData);
Cypress.env("editingVersionId", responseData.editing_version.id);
Cypress.env("environmentId", responseData.editorEnvironment.id);
// You can log it to check if the env var is set correctly
cy.log(Cypress.env("apiResponseData"));
});
cy.get(componentSelector, { timeout: 10000 });
}
@ -267,6 +288,7 @@ Cypress.Commands.add("apiAddQuery", (queryName, query, dataQueryId) => {
Cypress.Commands.add(
"apiAddQueryToApp",
(queryName, options, dsName, dsKind) => {
cy.log(`${Cypress.env("server_host")}/api/data-queries/data-sources/${Cypress.env(dsKind)}/versions/${Cypress.env("editingVersionId")}`)
cy.getCookie("tj_auth_token", { log: false }).then((cookie) => {
const authToken = `tj_auth_token=${cookie.value}`;
const workspaceId = Cypress.env("workspaceId");
@ -286,7 +308,7 @@ Cypress.Commands.add(
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries`,
url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${Cypress.env(dsKind)}/versions/${Cypress.env("editingVersionId")}`,
headers: {
"Content-Type": "application/json",
Cookie: authToken,
@ -627,10 +649,11 @@ Cypress.Commands.add("apiAddDataToTable", (tableName, data) => {
});
Cypress.Commands.add("apiGetDataSourceIdByName", (dataSourceName) => {
const workspaceId = Cypress.env("workspaceId");
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/data-sources`,
url: `${Cypress.env("server_host")}/api/data-sources/${workspaceId}`,
headers: headers,
}).then((response) => {
expect(response.status).to.equal(200);
@ -665,7 +688,7 @@ Cypress.Commands.add(
name: dataSourceName,
options: [
{ key: "connection_type", value: "manual", encrypted: false },
{ key: "host", value: "35.202.183.199" },
{ key: "host", value: "35.238.9.114" },
{ key: "port", value: 5432 },
{ key: "database", value: "student" },
{ key: "username", value: "postgres" },

View file

@ -15,15 +15,11 @@ const API_ENDPOINT =
Cypress.Commands.add(
"appUILogin",
(email = "dev@tooljet.io", password = "password") => {
cy.visit("/");
cy.wait(1000);
cy.clearAndType(onboardingSelectors.loginEmailInput, email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, password);
cy.get(onboardingSelectors.signInButton).click();
cy.intercept("GET", API_ENDPOINT).as("library_apps");
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
cy.wait("@library_apps");
cy.wait(2000);
cy.get('[data-cy="main-wrapper"]', { timeout: 10000 }).should("be.visible");
}
);
@ -400,36 +396,39 @@ Cypress.Commands.add("getPosition", (componentName) => {
Cypress.Commands.add("defaultWorkspaceLogin", () => {
cy.apiLogin();
// cy.intercept("GET", API_ENDPOINT).as("library_apps");
cy.visit("/my-workspace");
cy.intercept("GET", API_ENDPOINT).as("library_apps");
cy.wait(2000)
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
cy.wait("@library_apps");
// });
// cy.wait("@library_apps");
});
Cypress.Commands.add(
"visitSlug",
({
actualUrl,
currentUrl = `${Cypress.config("baseUrl")}/error/unknown`,
errorUrls = [
`${Cypress.config("baseUrl")}/error/unknown`,
`${Cypress.config("baseUrl")}/error/restricted`,
],
}) => {
// Ensure actualUrl is provided
if (!actualUrl) {
throw new Error("actualUrl is required for visitSlug command.");
}
cy.visit(actualUrl);
// Dynamically wait for the correct URL or handle navigation errors
cy.url().then((url) => {
if (url === currentUrl) {
cy.log(`Navigation resulted in unexpected URL: ${url}. Retrying...`);
if (errorUrls.includes(url)) {
cy.log(`Navigation resulted in error URL: ${url}. Retrying...`);
cy.visit(actualUrl);
cy.wait(1000);
}
});
}
);
Cypress.Commands.add("releaseApp", () => {
if (Cypress.env("environment") !== "Community") {
cy.get(commonEeSelectors.promoteButton).click();
@ -520,16 +519,6 @@ Cypress.Commands.add("verifyElement", (selector, text, eqValue) => {
element.should("be.visible").and("have.text", text);
});
Cypress.Commands.add("loginWithCredentials", (email, password) => {
cy.get(onboardingSelectors.loginEmailInput, { timeout: 20000 }).should(
"be.visible"
);
cy.clearAndType(onboardingSelectors.loginEmailInput, email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, password);
cy.get(onboardingSelectors.signInButton).click();
cy.wait(3000);
cy.get(commonSelectors.pageLogo).should("be.visible");
});
Cypress.Commands.add("getAppId", (appName) => {
cy.task("dbConnection", {

View file

@ -259,7 +259,7 @@ export const commonSelectors = {
cloneAppTitle: '[data-cy="clone-app-title"]',
cloneAppButton: '[data-cy="clone-app"]',
appNameErrorLabel: '[data-cy="app-name-error-label"]',
importAppTitle: '[data-cy="import-app-title"]',
importAppTitle: '[data-cy="import-an-app"]',
importAppButton: '[data-cy="import-app"]',
chooseFromTemplateButton: '[data-cy="choose-from-template-button"]',
CreateAppFromTemplateButton: '[data-cy="create-new-app-from-template-title"]',

View file

@ -1,7 +1,7 @@
export const multipageSelector = {
sidebarPageButton: '[data-cy="left-sidebar-page-button"]',
pagesLabel: '[data-cy="label-pages"]',
addPageIcon: '[title="Add Page"]',
addPageIcon: '[data-cy="add-page-button"]',
searchPageIcon: '[title="Search"]',
pagesPinIcon: '[title="Pin"]',

View file

@ -13,7 +13,7 @@ export const dataSourceText = {
? "Databases (20)"
: "Databases (18)";
},
allApis: "APIs (20)",
allApis: "APIs (21)",
allCloudStorage: "Cloud Storages (4)",
pluginsLabelAndCount: "Plugins (0)",

View file

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

View file

@ -4,7 +4,7 @@ export const workspaceConstantsText = {
secretsConstantInfo: "To resolve a secret workspace constant use {{secrets.access_token}}Read documentation",
emptyStateHeader: "No Workspace constants yet",
emptyStateText:
"Use workspace constants seamlessly in both the app builder and data source connections across ToolJet.",
"Use workspace constants seamlessly within both the app builder and data source connections across the platform.",
addNewConstantButton: "+ Create new constant",
addConstatntText: "Add new constant in production ",
constantCreatedToast: (type) => { return `${type} constant created successfully!` },

View file

@ -351,7 +351,7 @@ describe("Text Input", () => {
).should("have.css", "border-radius", "20px");
});
it.skip("should verify the app preview", () => {});
it.skip("should verify the app preview", () => { });
it("should verify CSA", () => {
const data = {};

View file

@ -43,7 +43,7 @@ describe("Editor title", () => {
cy.apiDeleteApp();
});
it("should verify titles", () => {
cy.url().should("include", "/tjs-workspace");
cy.url().should("include", "/tooljets-workspace");
// cy.title().should("eq", "Dashboard | ToolJet");
cy.title().should("eq", "ToolJet");
@ -56,7 +56,7 @@ describe("Editor title", () => {
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.url().should("include", `/applications/${Cypress.env("appId")}`);
cy.title().should("eq", `${data.appName} | ToolJet`);
// cy.title().should("eq", `${data.appName} | ToolJet`);
// cy.title().should("eq", `Preview - ${data.appName} | ToolJet`);
cy.go("back");

View file

@ -76,7 +76,7 @@ describe('Button Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Button-App`);
cy.openApp();
cy.dragAndDropWidget("Button", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {
@ -90,7 +90,7 @@ describe('Button Component Tests', () => {
});
it('should verify all the events from the button', () => {
it.skip('should verify all the events from the button', () => {
const events = [
{ event: "On hover", message: "On hover Event" },
{ event: "On Click", message: "On Click Event" },
@ -110,7 +110,7 @@ describe('Button Component Tests', () => {
verifyTextInputEvents(textInputSelector);
});
it('should verify all the CSA from button', () => {
it.skip('should verify all the CSA from button', () => {
addMultiEventsWithAlert([
{ event: "On hover", message: "On hover Event" },
{ event: "On Click", message: "On Click Event" },

View file

@ -84,7 +84,7 @@ describe('Checkbox Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Checkbox-App`);
cy.openApp();
cy.dragAndDropWidget("Checkbox", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {
@ -98,7 +98,7 @@ describe('Checkbox Component Tests', () => {
});
it('should verify all the events from the Checkbox', () => {
it.skip('should verify all the events from the Checkbox', () => {
const events = [
{ event: "On Change", message: "On Change Event" },
];
@ -118,7 +118,7 @@ describe('Checkbox Component Tests', () => {
verifyTextInputEvents(textInputSelector);
});
it('should verify all the CSA from Checkbox', () => {
it.skip('should verify all the CSA from Checkbox', () => {
const events = [
{ event: "On Change", message: "On Change Event" },
];

View file

@ -93,7 +93,7 @@ describe('Dropdown Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Dropdown-App`);
cy.openApp();
cy.dragAndDropWidget("Dropdown", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {

View file

@ -91,7 +91,7 @@ describe("Global Actions", () => {
cy.waitForAutoSave();
addInputOnQueryField("runjs", "actions.showModal('modal1');");
query("run");
cy.get('[data-cy="modal-title"]').should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
addInputOnQueryField("runjs", "actions.closeModal('modal1');");
query("run");
@ -114,7 +114,7 @@ describe("Global Actions", () => {
"actions.setLocalStorage('localStorage','data from runjs');"
);
query("run");
cy.wait(500)
cy.getAllLocalStorage().then((result) => {
expect(result[Cypress.config().baseUrl].localStorage).to.deep.equal(
"data from runjs"

View file

@ -96,7 +96,7 @@ describe('Multiselect Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Multiselect-App`);
cy.openApp();
cy.dragAndDropWidget("Multiselect", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {

View file

@ -87,7 +87,7 @@ describe('Number Input Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Numberinput-App`);
cy.openApp();
cy.dragAndDropWidget("Number Input", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {
@ -101,7 +101,7 @@ describe('Number Input Component Tests', () => {
});
it('should verify all the events from the number input', () => {
it.skip('should verify all the events from the number input', () => {
const events = [
{ event: "On Focus", message: "On Focus Event" },
{ event: "On Blur", message: "On Blur Event" },
@ -129,7 +129,7 @@ describe('Number Input Component Tests', () => {
inputEvents(inputSelector);
});
it('should verify all the CSA from number input', () => {
it.skip('should verify all the CSA from number input', () => {
const actions = [
{ event: "On click", action: "Set visibility", valueToggle: "{{false}}" }, //b1
{ event: "On click", action: "Set visibility", valueToggle: "{{true}}" },//b2

View file

@ -87,7 +87,7 @@ describe('Password Input Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Passwordinput-App`);
cy.openApp();
cy.dragAndDropWidget("Password Input", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {
@ -101,7 +101,7 @@ describe('Password Input Component Tests', () => {
});
it('should verify all the events from the password input', () => {
it.skip('should verify all the events from the password input', () => {
const events = [
{ event: "On Focus", message: "On Focus Event" },
{ event: "On Blur", message: "On Blur Event" },

View file

@ -95,10 +95,10 @@ describe('Text Input Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Textinput-App`);
cy.openApp();
cy.dragAndDropWidget("Text Input", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {
it.skip('should verify all the exposed values on inspector', () => {
cy.get(commonWidgetSelector.sidebarinspector).click();
cy.get(".tooltip-inner").invoke("hide");
@ -109,7 +109,7 @@ describe('Text Input Component Tests', () => {
});
it('should verify all the events from the text input', () => {
it.skip('should verify all the events from the text input', () => {
const events = [
{ event: "On Focus", message: "On Focus Event" },
{ event: "On Blur", message: "On Blur Event" },
@ -137,7 +137,7 @@ describe('Text Input Component Tests', () => {
verifyTextInputEvents(textInputSelector);
});
it('should verify all the CSA from text input', () => {
it.skip('should verify all the CSA from text input', () => {
const actions = [
{ event: "On click", action: "Set visibility", valueToggle: "{{false}}" }, //b1
{ event: "On click", action: "Visibility", valueToggle: "{{true}}" },//b2

View file

@ -80,7 +80,7 @@ describe('ToggleSwitch Component Tests', () => {
cy.apiCreateApp(`${fake.companyName}-Toggle-App`);
cy.openApp();
cy.dragAndDropWidget("Toggle Switch", 50, 50);
cy.get('[data-cy="query-manager-collapse-button"]').click();
cy.get('[data-cy="query-manager-toggle-button"]').click();
});
it('should verify all the exposed values on inspector', () => {
@ -137,6 +137,8 @@ describe('ToggleSwitch Component Tests', () => {
cy.get(commonWidgetSelector.draggableWidget(component)).should("not.be.visible");
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
cy.wait(500);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(component)).should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("button3")).click();

View file

@ -17,6 +17,7 @@ describe("Editor- Inspector", () => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-inspector-App`);
cy.openApp("?key=value");
cy.viewport(1800, 1800);
});
it("should verify the values of inspector", () => {
@ -45,14 +46,14 @@ describe("Editor- Inspector", () => {
cy.apiDeleteApp();
});
it("should verify dynamic items", () => {
it.skip("should verify dynamic items", () => {
cy.get(commonWidgetSelector.sidebarinspector).click();
cy.get(".tooltip-inner").invoke("hide");
cy.get(multipageSelector.sidebarPageButton).click();
addNewPage("test_page");
cy.dragAndDropWidget("Button", 500, 500);
cy.dragAndDropWidget("Button", 100, 100);
selectEvent("On click", "Switch page");
cy.get('[data-cy="switch-page-label-and-input"] > .select-search').click().type("home{enter}");
@ -72,7 +73,9 @@ describe("Editor- Inspector", () => {
cy.dragAndDropWidget("Button", 500, 300);
selectEvent("On click", "Set variable");
addSupportCSAData("event-key", "globalVar");
cy.wait(500)
addSupportCSAData("variable", "globalVar");
cy.wait(500)
cy.forceClickOnCanvas();
cy.waitForAutoSave();
@ -141,17 +144,17 @@ describe("Editor- Inspector", () => {
cy.dragAndDropWidget("Button", 500, 300);
cy.get(commonWidgetSelector.sidebarinspector).click();
openNode("components");
cy.get(`[data-cy="inspector-node-button1"] > .mx-1`).realHover();
cy.get('[style="height: 13px; width: 13px;"] > img').click();
cy.get(`[data-cy="inspector-node-button1"] > .mx-1`).eq(0).realHover();
cy.get('[style="height: 13px; width: 13px;"] > img').last().click();
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
cy.apiDeleteApp();
});
it("should verify deletion of component from inspector", () => {
it.skip("should verify deletion of component from inspector", () => {
cy.dragAndDropWidget("button", 500, 500);
cy.get(commonWidgetSelector.sidebarinspector).click();
deleteComponentFromInspector("button1");
cy.verifyToastMessage(`[class=go3958317564]`, "Component deleted! ( + Z to undo)");
cy.verifyToastMessage(`[class=go3958317564]`, "Component deleted! (ctrl + Z to undo)");
navigateToCreateNewVersionModal((currentVersion = "v1"));
createNewVersion((newVersion = ["v2"]), (versionFrom = "v1"));

View file

@ -14,6 +14,7 @@ describe("Chaining of queries", () => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-chaining-App`);
cy.openApp();
cy.apiFetchDataSourcesId()
cy.viewport(1800, 1800);
cy.dragAndDropWidget("Button");
resizeQueryPanel("80");
@ -57,7 +58,7 @@ describe("Chaining of queries", () => {
);
cy.apiCreateGDS(
"http://localhost:3000/api/v2/data_sources",
`http://localhost:3000/api/data-sources`,
`cypress-${dsName}-qc-postgresql`,
"postgresql",
[
@ -68,8 +69,10 @@ describe("Chaining of queries", () => {
{ key: "password", value: Cypress.env("pg_password"), encrypted: true },
{ key: "ssl_enabled", value: false, encrypted: false },
{ key: "ssl_certificate", value: "none", encrypted: false },
{ key: "connection_type", value: "manual", encrypted: false }
]
);
cy.log("Data source created");
cy.apiAddQueryToApp(
"psql",
{
@ -92,8 +95,17 @@ describe("Chaining of queries", () => {
chainQuery("restapi", "tjdb");
addSuccessNotification("restapi");
cy.get(`[data-cy="list-query-tjdb"]`).click();
cy.get('[data-cy="query-tab-settings"]').click();
selectEvent("Query Failure", "Show Alert");
cy.get('[data-cy="debounce-input-field"]')
.click()
.type(`{selectAll}{backspace}2000{enter}`);
cy.wait(1000)
cy.get('[data-cy="query-tab-setup"]').click();
openEditorSidebar(buttonText.defaultWidgetName);
selectEvent("On Click", "Run Query", 1, `[data-cy="add-event-handler"]`, 1);
selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0);
cy.wait(500);
cy.get('[data-cy="query-selection-field"]')
.click()
@ -105,11 +117,13 @@ describe("Chaining of queries", () => {
cy.verifyToastMessage(commonSelectors.toastMessage, "psql");
cy.verifyToastMessage(commonSelectors.toastMessage, "runjs");
cy.verifyToastMessage(commonSelectors.toastMessage, "runpy");
cy.wait(500);
cy.verifyToastMessage(commonSelectors.toastMessage, "restapi");
cy.verifyToastMessage(commonSelectors.toastMessage, "Invalid operation");
// cy.verifyToastMessage(commonSelectors.toastMessage, "Hello World");
});
it("should verify query duplication", () => {
it.skip("should verify query duplication", () => {
const data = {};
let dsName = fake.companyName;
data.customText = randomString(12);
@ -133,7 +147,7 @@ describe("Chaining of queries", () => {
addSuccessNotification("runjs");
openEditorSidebar(buttonText.defaultWidgetName);
selectEvent("On Click", "Run Query", 1, `[data-cy="add-event-handler"]`, 1);
selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0);
cy.wait(500);
cy.get('[data-cy="query-selection-field"]')
.click()

View file

@ -54,7 +54,7 @@ describe("RunJS", () => {
cy.apiDeleteApp();
});
it("should verify global and page data", () => {
it.skip("should verify global and page data", () => {
const data = {};
data.customText = randomString(12);
@ -147,9 +147,9 @@ describe("RunJS", () => {
"runjs",
"actions.showAlert('success', 'alert from runjs');"
);
cy.get('[data-cy="query-tab-Settings"]').click();
cy.get('[data-cy="query-tab-settings"]').click();
changeQueryToggles("run-on-app-load");
cy.wait(`@editQuery`);
// cy.wait(`@editQuery`);
cy.waitForAutoSave();
cy.waitForAutoSave();
cy.reload();
@ -159,9 +159,9 @@ describe("RunJS", () => {
"alert from runjs",
false
);
cy.get('[data-cy="query-tab-Settings"]').click();
cy.get('[data-cy="query-tab-settings"]').click();
changeQueryToggles("confirmation-before-run");
cy.wait(`@editQuery`);
// cy.wait(`@editQuery`);
cy.waitForAutoSave();
cy.reload();
cy.get('[data-cy="modal-message"]').verifyVisibleElement(
@ -172,12 +172,12 @@ describe("RunJS", () => {
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runjs");
resizeQueryPanel("80");
cy.get('[data-cy="query-tab-Settings"]').click();
cy.get('[data-cy="query-tab-settings"]').click();
changeQueryToggles("notification-on-success");
cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror(
"Success alert"
);
cy.get('[data-cy="query-tab-Setup"]').click();
cy.get('[data-cy="query-tab-setup"]').click();
cy.get('[data-cy="runjs-input-field"]').realClick();
cy.wait(1000);
cy.waitForAutoSave();

View file

@ -59,7 +59,7 @@ describe("runpy", () => {
cy.apiDeleteApp();
});
it.only("should verify actions", () => {
it.skip("should verify actions", () => {
const data = {};
data.customText = randomString(12);
@ -120,18 +120,18 @@ actions.unsetPageVariable('pageVar')`
cy.waitForAutoSave();
addInputOnQueryField("runpy", "actions.showModal('modal1')");
query("run");
cy.get('[data-cy="modal-title"]').should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get('[data-cy="runpy-input-field"]').click({ force: true });
addInputOnQueryField("runpy", "actions.closeModal('modal1')");
cy.wait(`@editQuery`);
// cy.wait(`@editQuery`);
cy.waitForAutoSave();
query("run");
waitForQueryAction("run");
cy.notVisible('[data-cy="modal-title"]');
addInputOnQueryField("runpy", "actions.copyToClipboard('data from runpy')");
cy.wait(`@editQuery`);
// cy.wait(`@editQuery`);
cy.waitForAutoSave();
query("run");
waitForQueryAction("run");
@ -144,7 +144,7 @@ actions.unsetPageVariable('pageVar')`
"runpy",
"actions.setLocalStorage('localStorage','data from runpy')"
);
cy.wait(`@editQuery`);
// cy.wait(`@editQuery`);
cy.waitForAutoSave();
query("run");
waitForQueryAction("run");
@ -155,17 +155,17 @@ actions.unsetPageVariable('pageVar')`
);
});
addInputOnQueryField(
"runpy",
"actions.generateFile('runpycsv', 'csv', [{ 'name': 'John', 'email': 'john@tooljet.com' }])"
);
query("run");
// addInputOnQueryField( //Need fix asap
// "runpy",
// "actions.generateFile('runpycsv', 'csv', [{ 'name': 'John', 'email': 'john@tooljet.com' }])"
// );
// query("run");
cy.wait(3000);
// cy.wait(3000);
cy.readFile("cypress/downloads/runpycsv.csv", "utf-8")
.should("contain", "name,email")
.and("contain", "John,john@tooljet.com");
// cy.readFile("cypress/downloads/runpycsv.csv", "utf-8")
// .should("contain", "name,email")
// .and("contain", "John,john@tooljet.com");
// addInputOnQueryField(
// "runpy",
@ -174,7 +174,7 @@ actions.unsetPageVariable('pageVar')`
// query("run");
addInputOnQueryField("runpy", "actions.logout()");
cy.wait(`@editQuery`);
// cy.wait(`@editQuery`);
cy.wait(200);
cy.waitForAutoSave();
query("run");
@ -184,7 +184,7 @@ actions.unsetPageVariable('pageVar')`
);
});
it("should verify global and page data", () => {
it.skip("should verify global and page data", () => {
const data = {};
data.customText = randomString(12);
@ -273,20 +273,20 @@ actions.unsetPageVariable('pageVar')`
"runpy",
"actions.showAlert('success', 'alert from runpy');"
);
cy.get('[data-cy="query-tab-Settings"]').click();
cy.wait("@editQuery");
cy.get('[data-cy="query-tab-settings"]').click();
// cy.wait("@editQuery");
cy.wait(200);
cy.waitForAutoSave();
changeQueryToggles("run-on-app-load");
cy.wait("@editQuery");
// cy.wait("@editQuery");
cy.waitForAutoSave();
cy.reload();
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runpy");
cy.get('[data-cy="query-tab-Settings"]').click();
cy.get('[data-cy="query-tab-settings"]').click();
changeQueryToggles("confirmation-before-run");
cy.wait("@editQuery");
// cy.wait("@editQuery");
cy.wait(200);
cy.waitForAutoSave();
cy.reload();
@ -297,13 +297,13 @@ actions.unsetPageVariable('pageVar')`
cy.get('[data-cy="modal-confirm-button"]').realClick();
cy.verifyToastMessage(commonSelectors.toastMessage, "alert from runpy");
cy.get('[data-cy="query-tab-Settings"]').click();
cy.get('[data-cy="query-tab-settings"]').click();
changeQueryToggles("notification-on-success");
cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror(
"Success alert"
);
cy.forceClickOnCanvas();
cy.wait("@editQuery");
// cy.wait("@editQuery");
cy.wait(200);
cy.waitForAutoSave();
cy.reload();

View file

@ -26,7 +26,6 @@ describe("Data source amazon athena", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on amazon athena connection form", () => {

View file

@ -26,7 +26,6 @@ describe("Data source amazon ses", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on amazonses connection form", () => {

View file

@ -26,7 +26,6 @@ describe("Data source AppWrite", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on appwrite connection form", () => {

View file

@ -26,7 +26,6 @@ describe("Data source AWS Lambda", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on AWS Lambda connection form", () => {

View file

@ -27,7 +27,6 @@ describe("Data source AWS Textract", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on AWS Textract connection form", () => {

View file

@ -18,7 +18,7 @@ data.customText = fake.randomSentence;
describe("Data source Azure Blob Storage", () => {
beforeEach(() => {
cy.appUILogin();
cy.intercept("GET", "/api/v2/data_sources");
cy.defaultWorkspaceLogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -26,7 +26,6 @@ describe("Data source baserow", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("POST", "/api/data_queries").as("createQuery");
});
it("Should verify elements on baserow connection form", () => {

View file

@ -18,7 +18,6 @@ describe("Data source BigQuery", () => {
beforeEach(() => {
cy.appUILogin();
cy.defaultWorkspaceLogin();
cy.intercept("GET", "/api/v2/data_sources");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");

View file

@ -5,7 +5,7 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
import { restAPISelector } from "Selectors/restAPI";
import { restAPIText } from "Texts/restAPI";
import { fillDataSourceTextField } from "Support/utils/postgreSql";
import { createAndRunRestAPIQuery } from "Support/utils/restAPI";
const data = {};
const authenticationDropdownSelector =
@ -19,8 +19,8 @@ const clientAuthenticationDropdown =
describe("Data source Rest API", () => {
beforeEach(() => {
cy.apiLogin();
cy.defaultWorkspaceLogin();
cy.intercept("GET", "/api/v2/data_sources");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
@ -331,7 +331,8 @@ describe("Data source Rest API", () => {
cy.verifyToastMessage(commonSelectors.toastMessage, "Data Source Saved");
deleteDatasource(`cypress-${data.dataSourceName}-restapi`);
});
it("Should verify connection for Rest API", () => {
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`,
@ -366,18 +367,89 @@ describe("Data source Rest API", () => {
]
);
cy.reload();
// cy.apiCreateApp(`${fake.companyName}-restAPI-App`);
// cy.apiAddQueryToApp(
// "restapi1",
// {
// method: "get",
// url: "",
// url_params: [["", ""]],
// headers: [["", ""]],
// cookies: [["", ""]],
// },
// `cypress-${data.dataSourceName}-restapi`,
// "restapi"
// );
cy.apiCreateApp(`${fake.companyName}-restAPI-App`);
cy.openApp();
createAndRunRestAPIQuery(
"get_restapi",
`cypress-${data.dataSourceName}-restapi`,
"GET",
"/api/users"
);
createAndRunRestAPIQuery(
"post_restapi",
`cypress-${data.dataSourceName}-restapi`,
"POST",
"",
[["Content-Type", "application/json"]],
[],
{
price: 200,
name: "Violin",
},
true,
"/api/users"
);
cy.readFile("cypress/fixtures/restAPI/storedId.json").then(
(postResponseID) => {
const id1 = postResponseID.id;
createAndRunRestAPIQuery(
"put_restapi_id",
`cypress-${data.dataSourceName}-restapi`,
"PUT",
"",
[["Content-Type", "application/json"]],
[],
{
price: 500,
name: "Guitar",
},
true,
`/api/users/${id1}`
);
}
);
cy.readFile("cypress/fixtures/restAPI/storedId.json").then(
(putResponseID) => {
const id2 = putResponseID.id;
createAndRunRestAPIQuery(
"patch_restapi_id",
`cypress-${data.dataSourceName}-restapi`,
"PATCH",
"",
[["Content-Type", "application/json"]],
[],
{ price: 999 },
true,
`/api/users/${id2}`
);
}
);
cy.readFile("cypress/fixtures/restAPI/storedId.json").then(
(patchResponseID) => {
const id3 = patchResponseID.id;
createAndRunRestAPIQuery(
"get_restapi_id",
`cypress-${data.dataSourceName}-restapi`,
"GET",
"",
[],
[],
true,
`/api/users/${id3}`
);
createAndRunRestAPIQuery(
"delete_restapi_id",
`cypress-${data.dataSourceName}-restapi`,
"DELETE",
"",
[],
[],
true,
`/api/users/${id3}`
);
}
);
});
});

View file

@ -0,0 +1,219 @@
import { fake } from "Fixtures/fake";
import { commonSelectors } from "Selectors/common";
import { importSelectors } from "Selectors/exportImport";
import { commonText } from "Texts/common";
import { exportAppModalText } from "Texts/exportImport";
import {
clickOnExportButtonAndVerify,
exportAllVersionsAndVerify,
verifyElementsOfExportModal,
} from "Support/utils/exportImport";
import { selectAppCardOption, closeModal } from "Support/utils/common";
describe("App Export", () => {
const TEST_DATA = {
appFiles: {
multiVersion: "cypress/fixtures/templates/three-versions.json",
singleVersion: "cypress/fixtures/templates/one_version.json",
},
};
let data;
data = {
workspaceName: fake.firstName,
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
appName: `${fake.companyName}-IE-App`,
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
};
beforeEach(() => {
data = {
workspaceName: fake.firstName,
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
appName: `${fake.companyName}-IE-App`,
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
};
cy.exec("mkdir -p ./cypress/downloads/");
cy.wait(3000);
cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.apiLogout();
});
it("Verify the elements of export dialog box", () => {
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("walkthroughCompleted", "true");
});
cy.apiLogin();
cy.visit(`${data.workspaceSlug}`);
cy.get(importSelectors.importOptionInput)
.eq(0)
.selectFile(TEST_DATA.appFiles.multiVersion, {
force: true,
});
cy.wait(1500);
cy.clearAndType(commonSelectors.appNameInput, data.appName);
cy.get(importSelectors.importAppButton).click();
cy.wait(3000);
cy.backToApps();
// Select the app card option to export the app
selectAppCardOption(
data.appName,
commonSelectors.appCardOptions(commonText.exportAppOption)
);
// Verify the elements of the export modal
verifyElementsOfExportModal("v3", ["v2", "v1"], [true, false, false]);
// Close the modal
closeModal(exportAppModalText.modalCloseButton);
// Ensure the modal title is no longer visible
cy.get(
commonSelectors.modalTitle(exportAppModalText.selectVersionTitle)
).should("not.exist");
// Re-open the export modal and click the export button
selectAppCardOption(
data.appName,
commonSelectors.appCardOptions(commonText.exportAppOption)
);
clickOnExportButtonAndVerify(exportAppModalText.exportAll, data.appName);
cy.exec("ls ./cypress/downloads/").then((result) => {
const downloadedAppExportFileName = result.stdout.split("\n")[0];
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
// Ensure the file name contains the expected app export name
expect(downloadedAppExportFileName).to.contain(
data.appName.toLowerCase()
);
// Read and validate the exported JSON file
cy.readFile(filePath).then((appData) => {
// Validate the app name
const appNameFromFile = appData.app[0].definition.appV2.name;
expect(appNameFromFile).to.equal(data.appName);
// Validate the schema for the student table in tooljetdb
const tooljetDatabase = appData.tooljet_database.find(
(db) => db.table_name === "student"
);
expect(tooljetDatabase).to.exist;
expect(tooljetDatabase.schema).to.exist;
// Validate components and queries
const components = appData.app[0].definition.appV2.components;
const text2Component = components.find(
(component) => component.name === "text2"
);
expect(text2Component).to.exist;
expect(text2Component.properties.text.value).to.equal(
"{{constants.pageHeader}}"
);
const textinput1 = components.find(
(component) => component.name === "textinput1"
);
expect(textinput1).to.exist;
expect(textinput1.properties.value.value).to.include("queries");
const textinput2 = components.find(
(component) => component.name === "textinput2"
);
expect(textinput2).to.exist;
expect(textinput2.properties.value.value).to.include("queries");
const textinput3 = components.find(
(component) => component.name === "textinput3"
);
expect(textinput3).to.exist;
expect(textinput3.properties.value.value).to.include("queries");
// Validate the data queries
const dataQueries = appData.app[0].definition.appV2.dataQueries;
const postgresqlQuery = dataQueries.find(
(query) => query.name === "postgresql1"
);
expect(postgresqlQuery).to.exist;
expect(postgresqlQuery.options.query).to.include(
"Select * from {{secrets.db_name}}"
);
const restapiQuery = dataQueries.find(
(query) => query.name === "restapi1"
);
expect(restapiQuery).to.exist;
expect(restapiQuery.options.url).to.equal(
"https://jsonplaceholder.typicode.com/users/1"
);
const tooljetdbQuery = dataQueries.find(
(query) => query.name === "tooljetdb1"
);
expect(tooljetdbQuery).to.exist;
expect(tooljetdbQuery.options.operation).to.equal("list_rows");
// Ensure appVersions exists
const appVersions = appData.app[0].definition.appV2.appVersions;
expect(appVersions).to.exist;
// Map and verify app version names
const versionNames = appVersions.map((version) => version.name);
expect(versionNames).to.include.members(["v1", "v2", "v3"]);
});
});
cy.exec("cd ./cypress/downloads/ && rm -rf *");
selectAppCardOption(
data.appName,
commonSelectors.appCardOptions(commonText.exportAppOption)
);
cy.get(`[data-cy="v1-radio-button"]`).check();
cy.get(
commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion)
).click();
cy.exec("ls ./cypress/downloads/").then((result) => {
const downloadedAppExportFileName = result.stdout.split("\n")[0];
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
// Ensure the file name contains the expected app export name
expect(downloadedAppExportFileName).to.contain(
data.appName.toLowerCase()
);
// Read and validate the exported JSON file
cy.readFile(filePath).then((appData) => {
// Validate the app name
const appNameFromFile = appData.app[0].definition.appV2.name;
expect(appNameFromFile).to.equal(data.appName);
});
});
});
it.skip("Verify 'Export app' functionality of an application inside app editor", () => {
data.appName2 = `${fake.companyName}-App`;
cy.apiCreateApp(data.appName2);
cy.openApp(data.appName2);
cy.dragAndDropWidget("Text Input", 50, 50);
cy.get('[data-cy="left-sidebar-settings-button"]').click();
cy.get('[data-cy="button-user-status-change"]').click();
verifyElementsOfExportModal("v1");
exportAllVersionsAndVerify(data.appName1, "v1");
});
});

View file

@ -0,0 +1,230 @@
import { fake } from "Fixtures/fake";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { appVersionSelectors, importSelectors } from "Selectors/exportImport";
import { dashboardSelector } from "Selectors/dashboard";
import { buttonText } from "Texts/button";
import { importText } from "Texts/exportImport";
import { importAndVerifyApp } from "Support/utils/exportImport";
import { switchVersionAndVerify } from "Support/utils/version";
describe("App Import Functionality", () => {
const TEST_DATA = {
toolJetImage: "cypress/fixtures/Image/tooljet.png",
invalidApp: "cypress/fixtures/templates/invalid_app.json",
invalidFile: "cypress/fixtures/templates/invalid_file.json",
appFiles: {
multiVersion: "cypress/fixtures/templates/three-versions.json",
singleVersion: "cypress/fixtures/templates/one_version.json",
},
};
let data;
beforeEach(() => {
cy.viewport(1200, 1300);
data = {
workspaceName: fake.firstName,
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
appName: `${fake.companyName}-IE-App`,
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
};
cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.apiLogout();
});
it("should verify app import functionality", () => {
cy.apiLogin();
cy.visit(`${data.workspaceSlug}`);
// Test invalid file import
cy.get(dashboardSelector.importAppButton).click();
importAndVerifyApp(
TEST_DATA.toolJetImage,
importText.couldNotImportAppToastMessage
);
cy.wait(500);
cy.get(dashboardSelector.importAppButton).click();
importAndVerifyApp(
TEST_DATA.invalidApp,
"Could not import: SyntaxError: Expected ',' or '}' after property value in JSON at position 246 (line 11 column 13)"
);
cy.wait(500);
// Test valid app import
cy.get(importSelectors.dropDownMenu).should("be.visible").click();
cy.get(importSelectors.importOptionLabel).verifyVisibleElement(
"have.text",
importText.importOption
);
cy.intercept("POST", "/api/v2/resources/import").as("importApp");
cy.get(importSelectors.importOptionInput)
.eq(0)
.selectFile(TEST_DATA.appFiles.multiVersion, {
force: true,
});
cy.wait(1500);
cy.get(importSelectors.importAppTitle).verifyVisibleElement(
"have.text",
"Import app"
);
cy.get(commonSelectors.appNameLabel).verifyVisibleElement(
"have.text",
"App name"
);
cy.get(commonSelectors.appNameInput)
.should("be.visible")
.and("have.value", "three-versions");
cy.get(commonSelectors.appNameInfoLabel).verifyVisibleElement(
"have.text",
"App name must be unique and max 50 characters"
);
cy.get(commonSelectors.cancelButton)
.should("be.visible")
.and("have.text", "Cancel");
cy.get(commonSelectors.importAppButton).verifyVisibleElement(
"have.text",
"Import app"
);
cy.get(importSelectors.importAppButton).click();
cy.get(".go3958317564")
.should("be.visible")
.and("have.text", importText.appImportedToastMessage);
// Verify imported app
cy.get(".driver-close-btn").click();
cy.wait(500);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
"three-versions"
);
// Configure app
cy.skipEditorPopover();
cy.dragAndDropWidget(buttonText.defaultWidgetText);
cy.get(appVersionSelectors.appVersionLabel).should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("button1")).should(
"be.visible"
);
cy.renameApp(data.appName);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
data.appName
);
cy.waitForAutoSave();
// Verify initial widget states
verifyCommonData({
text2: "",
textInput1: "",
textInput2: "Leanne Graham",
});
// cy.get(
// commonWidgetSelector.draggableWidget("textInput3")
// ).verifyVisibleElement("have.value", "");
// Setup database and data sources
cy.visit(`${data.workspaceSlug}/database`);
cy.get('[data-cy="student-table"]').verifyVisibleElement(
"have.text",
"student"
);
// cy.apiAddDataToTable("student", {
// name: "Paramu",
// country: "India",
// state: "Kerala",
// });
cy.visit(`${data.workspaceSlug}/data-sources`);
cy.get('[data-cy="postgresql-button"]').should("be.visible");
cy.apiUpdateDataSource("postgresql", "production", {
options: [
{
key: "password",
value: `${Cypress.env("pg_password")}`,
encrypted: true,
},
],
});
cy.apiCreateWsConstant(
"pageHeader",
"Import and Export",
["Global"],
["production"]
);
cy.apiCreateWsConstant("db_name", "persons", ["Secret"], ["production"]);
// Verify app after setup
cy.wait("@importApp").then((interception) => {
const appId = interception.response.body.imports.app[0].id;
cy.openApp(
"",
Cypress.env("workspaceId"),
appId,
commonWidgetSelector.draggableWidget("text2")
);
});
verifyCommonData({
text2: "Import and Export",
textInput1: "John",
textInput2: "Leanne Graham",
});
// cy.get(
// commonWidgetSelector.draggableWidget("textInput3")
// ).verifyVisibleElement("have.value", "India");
switchVersionAndVerify("v3", "v1");
verifyCommonData({
text2: "Import and Export",
textInput1: "John",
textInput2: "Leanne Graham",
});
cy.wait(1000);
cy.backToApps();
// Test single version import
cy.get(importSelectors.dropDownMenu).click();
importAndVerifyApp(TEST_DATA.appFiles.singleVersion);
// Verify final state
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
"one_version"
);
verifyCommonData({
text2: "Import and Export",
textInput1: "John",
textInput2: "Leanne Graham",
});
});
});
const verifyCommonData = (values) => {
cy.get(commonWidgetSelector.draggableWidget("text2")).verifyVisibleElement(
"have.text",
values.text2
);
cy.get(
commonWidgetSelector.draggableWidget("textInput1")
).verifyVisibleElement("have.value", values.textInput1);
cy.get(
commonWidgetSelector.draggableWidget("textInput2")
).verifyVisibleElement("have.value", values.textInput2);
};

View file

@ -1,419 +0,0 @@
import { fake } from "Fixtures/fake";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { appVersionSelectors, importSelectors } from "Selectors/exportImport";
import { commonText } from "Texts/common";
import { dashboardSelector } from "Selectors/dashboard";
import { buttonText } from "Texts/button";
import { exportAppModalText, importText } from "Texts/exportImport";
import {
clickOnExportButtonAndVerify,
exportAllVersionsAndVerify,
verifyElementsOfExportModal,
importAndVerifyApp,
} from "Support/utils/exportImport";
import { selectAppCardOption, closeModal } from "Support/utils/common";
import { switchVersionAndVerify } from "Support/utils/version";
describe("App Import Functionality", () => {
const TEST_DATA = {
toolJetImage: "cypress/fixtures/Image/tooljet.png",
invalidApp: "cypress/fixtures/templates/invalid_app.json",
invalidFile: "cypress/fixtures/templates/invalid_file.json",
appFiles: {
multiVersion: "cypress/fixtures/templates/three-versions.json",
singleVersion: "cypress/fixtures/templates/one_version.json",
},
};
let data;
const initializeData = () => {
const firstName = fake.firstName;
return {
workspaceName: firstName,
workspaceSlug: firstName.toLowerCase().replace(/\s+/g, "-"),
appName: `${fake.companyName}-IE-App`,
appReName: `${fake.companyName}-${fake.companyName}-IE-App`,
dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""),
};
};
data = initializeData();
before(() => {
cy.exec("mkdir -p ./cypress/downloads/");
cy.wait(3000);
});
beforeEach(() => {
cy.viewport(1200, 1300);
cy.apiLogin();
});
it("should verify app import functionality", () => {
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.apiLogout();
cy.apiLogin();
cy.visit(`${data.workspaceSlug}`);
// Test invalid file import
cy.get(dashboardSelector.importAppButton).click();
importAndVerifyApp(
TEST_DATA.toolJetImage,
importText.couldNotImportAppToastMessage
);
cy.wait(500);
cy.get(dashboardSelector.importAppButton).click();
importAndVerifyApp(
TEST_DATA.invalidApp,
"Could not import: SyntaxError: Expected ',' or '}' after property value in JSON at position 246 (line 11 column 13)"
);
cy.wait(500);
cy.get(dashboardSelector.importAppButton).click();
cy.get(importSelectors.importOptionInput)
.eq(0)
.selectFile(TEST_DATA.invalidFile, {
force: true,
});
cy.get(importSelectors.importAppTitle).should("be.visible");
cy.get(importSelectors.importAppButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"tooljet_version must be a string"
);
cy.wait(500);
// Test valid app import
cy.get(importSelectors.dropDownMenu).should("be.visible").click();
cy.get(importSelectors.importOptionLabel).verifyVisibleElement(
"have.text",
importText.importOption
);
cy.intercept("POST", "/api/v2/resources/import").as("importApp");
cy.get(importSelectors.importOptionInput)
.eq(0)
.selectFile(TEST_DATA.appFiles.multiVersion, {
force: true,
});
cy.wait(1500);
cy.get(importSelectors.importAppTitle).verifyVisibleElement(
"have.text",
"Import app"
);
cy.get(commonSelectors.appNameLabel).verifyVisibleElement(
"have.text",
"App name"
);
cy.get(commonSelectors.appNameInput)
.should("be.visible")
.and("have.value", "three-versions");
cy.get(commonSelectors.appNameInfoLabel).verifyVisibleElement(
"have.text",
"App name must be unique and max 50 characters"
);
cy.get(commonSelectors.cancelButton)
.should("be.visible")
.and("have.text", "Cancel");
cy.get(commonSelectors.importAppButton).verifyVisibleElement(
"have.text",
"Import app"
);
cy.get(importSelectors.importAppButton).click();
cy.get(".go3958317564")
.should("be.visible")
.and("have.text", importText.appImportedToastMessage);
// Verify imported app
cy.get(".driver-close-btn").click();
cy.wait(500);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
"three-versions"
);
// Configure app
cy.skipEditorPopover();
cy.dragAndDropWidget(buttonText.defaultWidgetText);
cy.get(appVersionSelectors.appVersionLabel).should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("button1")).should(
"be.visible"
);
cy.renameApp(data.appName);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
data.appName
);
cy.waitForAutoSave();
// Verify initial widget states
verifyCommonData({
text2: "",
textInput1: "",
textInput2: "Leanne Graham",
});
cy.get(
commonWidgetSelector.draggableWidget("textInput3")
).verifyVisibleElement("have.value", "");
// Setup database and data sources
cy.visit(`${data.workspaceSlug}/database`);
cy.get('[data-cy="student-table"]').verifyVisibleElement(
"have.text",
"student"
);
cy.apiAddDataToTable("student", {
name: "Paramu",
country: "India",
state: "Kerala",
});
cy.visit(`${data.workspaceSlug}/data-sources`);
cy.get('[data-cy="postgresql-button"]').should("be.visible");
cy.apiUpdateDataSource("postgresql", "production", {
options: [
{
key: "password",
value: `${Cypress.env("pg_password")}`,
encrypted: true,
},
],
});
cy.apiCreateWsConstant(
"pageHeader",
"Import and Export",
["Global"],
["production"]
);
cy.apiCreateWsConstant("db_name", "persons", ["Secret"], ["production"]);
// Verify app after setup
cy.wait("@importApp").then((interception) => {
const appId = interception.response.body.imports.app[0].id;
cy.openApp(
"",
Cypress.env("workspaceId"),
appId,
commonWidgetSelector.draggableWidget("text2")
);
});
verifyCommonData({
text2: "Import and Export",
textInput1: "John",
textInput2: "Leanne Graham",
});
cy.get(
commonWidgetSelector.draggableWidget("textInput3")
).verifyVisibleElement("have.value", "India");
switchVersionAndVerify("v3", "v1");
verifyCommonData({
text2: "Import and Export",
textInput1: "John",
textInput2: "Leanne Graham",
});
cy.wait(1000);
cy.backToApps();
// Test single version import
cy.get(importSelectors.dropDownMenu).click();
importAndVerifyApp(TEST_DATA.appFiles.singleVersion);
// Verify final state
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
"one_version"
);
verifyCommonData({
text2: "Import and Export",
textInput1: "John",
textInput2: "Leanne Graham",
});
});
it("Verify the elements of export dialog box", () => {
cy.exec("cd ./cypress/downloads/ && rm -rf *");
cy.visit(`${data.workspaceSlug}`);
// Select the app card option to export the app
selectAppCardOption(
data.appName,
commonSelectors.appCardOptions(commonText.exportAppOption)
);
// Verify the elements of the export modal
verifyElementsOfExportModal("v3", ["v2", "v1"], [true, false, false]);
// Close the modal
closeModal(exportAppModalText.modalCloseButton);
// Ensure the modal title is no longer visible
cy.get(
commonSelectors.modalTitle(exportAppModalText.selectVersionTitle)
).should("not.exist");
// Re-open the export modal and click the export button
selectAppCardOption(
data.appName,
commonSelectors.appCardOptions(commonText.exportAppOption)
);
clickOnExportButtonAndVerify(exportAppModalText.exportAll, data.appName);
cy.exec("ls ./cypress/downloads/").then((result) => {
const downloadedAppExportFileName = result.stdout.split("\n")[0];
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
// Ensure the file name contains the expected app export name
expect(downloadedAppExportFileName).to.contain(
data.appName.toLowerCase()
);
// Read and validate the exported JSON file
cy.readFile(filePath).then((appData) => {
// Validate the app name
const appNameFromFile = appData.app[0].definition.appV2.name;
expect(appNameFromFile).to.equal(data.appName);
// Validate the schema for the student table in tooljetdb
const tooljetDatabase = appData.tooljet_database.find(
(db) => db.table_name === "student"
);
expect(tooljetDatabase).to.exist;
expect(tooljetDatabase.schema).to.exist;
// Validate components and queries
const components = appData.app[0].definition.appV2.components;
const text2Component = components.find(
(component) => component.name === "text2"
);
expect(text2Component).to.exist;
expect(text2Component.properties.text.value).to.equal(
"{{constants.pageHeader}}"
);
const textinput1 = components.find(
(component) => component.name === "textinput1"
);
expect(textinput1).to.exist;
expect(textinput1.properties.value.value).to.include("queries");
const textinput2 = components.find(
(component) => component.name === "textinput2"
);
expect(textinput2).to.exist;
expect(textinput2.properties.value.value).to.include("queries");
const textinput3 = components.find(
(component) => component.name === "textinput3"
);
expect(textinput3).to.exist;
expect(textinput3.properties.value.value).to.include("queries");
// Validate the data queries
const dataQueries = appData.app[0].definition.appV2.dataQueries;
const postgresqlQuery = dataQueries.find(
(query) => query.name === "postgresql1"
);
expect(postgresqlQuery).to.exist;
expect(postgresqlQuery.options.query).to.include(
"Select * from {{secrets.db_name}}"
);
const restapiQuery = dataQueries.find(
(query) => query.name === "restapi1"
);
expect(restapiQuery).to.exist;
expect(restapiQuery.options.url).to.equal(
"https://jsonplaceholder.typicode.com/users/1"
);
const tooljetdbQuery = dataQueries.find(
(query) => query.name === "tooljetdb1"
);
expect(tooljetdbQuery).to.exist;
expect(tooljetdbQuery.options.operation).to.equal("list_rows");
// Ensure appVersions exists
const appVersions = appData.app[0].definition.appV2.appVersions;
expect(appVersions).to.exist;
// Map and verify app version names
const versionNames = appVersions.map((version) => version.name);
expect(versionNames).to.include.members(["v1", "v2", "v3"]);
});
});
cy.exec("cd ./cypress/downloads/ && rm -rf *");
selectAppCardOption(
data.appName,
commonSelectors.appCardOptions(commonText.exportAppOption)
);
cy.get(`[data-cy="v1-radio-button"]`).check();
cy.get(
commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion)
).click();
cy.exec("ls ./cypress/downloads/").then((result) => {
const downloadedAppExportFileName = result.stdout.split("\n")[0];
const filePath = `./cypress/downloads/${downloadedAppExportFileName}`;
// Ensure the file name contains the expected app export name
expect(downloadedAppExportFileName).to.contain(
data.appName.toLowerCase()
);
// Read and validate the exported JSON file
cy.readFile(filePath).then((appData) => {
// Validate the app name
const appNameFromFile = appData.app[0].definition.appV2.name;
expect(appNameFromFile).to.equal(data.appName);
});
});
});
it.skip("Verify 'Export app' functionality of an application inside app editor", () => {
data.appName2 = `${fake.companyName}-App`;
cy.apiCreateApp(data.appName2);
cy.openApp(data.appName2);
cy.dragAndDropWidget("Text Input", 50, 50);
cy.get('[data-cy="left-sidebar-settings-button"]').click();
cy.get('[data-cy="button-user-status-change"]').click();
verifyElementsOfExportModal("v1");
exportAllVersionsAndVerify(data.appName1, "v1");
});
});
const verifyCommonData = (values) => {
cy.get(commonWidgetSelector.draggableWidget("text2")).verifyVisibleElement(
"have.text",
values.text2
);
cy.get(
commonWidgetSelector.draggableWidget("textInput1")
).verifyVisibleElement("have.value", values.textInput1);
cy.get(
commonWidgetSelector.draggableWidget("textInput2")
).verifyVisibleElement("have.value", values.textInput2);
};

View file

@ -15,7 +15,6 @@ describe("App Slug", () => {
beforeEach(() => {
data.slug = `${fake.companyName.toLowerCase()}-app`;
data.appName = `${fake.companyName} App`;
cy.log(Cypress.env("workspaceId"));
cy.defaultWorkspaceLogin();
});
@ -25,133 +24,137 @@ describe("App Slug", () => {
cy.apiCreateApp(data.appName);
cy.wait(1000);
cy.apiLogout();
cy.log(Cypress.env("workspaceId"));
});
it("Verify app slug cases in global settings", () => {
cy.apiLogin("dev@tooljet.io", "password").then(() => {
const workspaceId = Cypress.env("workspaceId");
const appId = Cypress.env("appId");
cy.apiLogin();
const workspaceId = Cypress.env("workspaceId");
const appId = Cypress.env("appId");
cy.openApp("my-workspace");
cy.get(commonSelectors.leftSideBarSettingsButton).click();
cy.visit("/my-workspace");
cy.wait(1000);
// Verify initial state
cy.get(commonWidgetSelector.appSlugLabel).verifyVisibleElement(
"have.text",
"Unique app slug"
);
cy.get(commonWidgetSelector.appSlugInput).verifyVisibleElement(
"have.value",
Cypress.env("appId")
);
cy.get(commonWidgetSelector.appSlugInfoLabel).verifyVisibleElement(
"have.text",
"URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens"
);
cy.get(commonWidgetSelector.appLinkLabel).verifyVisibleElement(
"have.text",
"App link"
);
cy.get(commonWidgetSelector.appLinkField).verifyVisibleElement(
"have.text",
`${host}/${workspaceId}/apps/${appId}`
);
// Validate all error cases
verifySlugValidations(commonWidgetSelector.appSlugInput);
// Verify successful slug update
cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug);
verifySuccessfulSlugUpdate(workspaceId, data.slug);
// Verify persistence
cy.get('[data-cy="left-sidebar-debugger-button"]').click();
cy.get(commonSelectors.leftSideBarSettingsButton).click();
cy.get(commonWidgetSelector.appSlugInput).should("have.value", data.slug);
// Release and verify URLs
releaseApp();
verifyURLs(workspaceId, data.slug, false);
// Verify duplicate slug validation
cy.visit("/my-workspace");
cy.apiCreateApp(data.slug);
cy.openApp("my-workspace");
cy.get(commonSelectors.leftSideBarSettingsButton).click();
cy.get(commonWidgetSelector.appSlugInput).clear();
cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug);
cy.get(commonWidgetSelector.appSlugErrorLabel).verifyVisibleElement(
"have.text",
"This app slug is already taken."
);
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("walkthroughCompleted", "true");
});
cy.visit(`/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`);
cy.wait(1000);
cy.get(commonSelectors.leftSideBarSettingsButton).click();
// Verify initial state
cy.get(commonWidgetSelector.appSlugLabel).verifyVisibleElement(
"have.text",
"Unique app slug"
);
cy.get(commonWidgetSelector.appSlugInput).verifyVisibleElement(
"have.value",
Cypress.env("appId")
);
cy.get(commonWidgetSelector.appSlugInfoLabel).verifyVisibleElement(
"have.text",
"URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens"
);
cy.get(commonWidgetSelector.appLinkLabel).verifyVisibleElement(
"have.text",
"App link"
);
cy.get(commonWidgetSelector.appLinkField).verifyVisibleElement(
"have.text",
`${host}/${workspaceId}/apps/${appId}`
);
// Validate all error cases
verifySlugValidations(commonWidgetSelector.appSlugInput);
// Verify successful slug update
cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug);
verifySuccessfulSlugUpdate(workspaceId, data.slug);
// Verify persistence
cy.get('[data-cy="left-sidebar-debugger-button"]').click();
cy.get(commonSelectors.leftSideBarSettingsButton).click();
cy.get(commonWidgetSelector.appSlugInput).should("have.value", data.slug);
// Release and verify URLs
releaseApp();
verifyURLs(workspaceId, data.slug, false);
// Verify duplicate slug validation
cy.visit("/my-workspace");
cy.apiCreateApp(data.slug);
cy.openApp("my-workspace");
cy.get(commonSelectors.leftSideBarSettingsButton).click();
cy.get(commonWidgetSelector.appSlugInput).clear();
cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug);
cy.get(commonWidgetSelector.appSlugErrorLabel).verifyVisibleElement(
"have.text",
"This app slug is already taken."
);
});
it("Verify app slug cases in share modal", () => {
cy.apiLogin("dev@tooljet.io", "password").then(() => {
const workspaceId = Cypress.env("workspaceId");
cy.apiLogin();
const workspaceId = Cypress.env("workspaceId");
cy.apiCreateApp(data.appName);
cy.openApp("my-workspace");
cy.apiCreateApp(data.appName);
cy.openApp("my-workspace");
// Set up initial slug
cy.get(commonSelectors.leftSideBarSettingsButton).click();
cy.get(commonWidgetSelector.appSlugInput).clear();
cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug);
// Set up initial slug
cy.get(commonSelectors.leftSideBarSettingsButton).click();
cy.get(commonWidgetSelector.appSlugInput).clear();
cy.clearAndType(commonWidgetSelector.appSlugInput, data.slug);
releaseApp();
releaseApp();
// Verify share modal
cy.get(commonWidgetSelector.shareAppButton).click();
cy.get(commonWidgetSelector.appLink).verifyVisibleElement(
"have.text",
`${host}/applications/`
);
cy.get(commonWidgetSelector.appNameSlugInput).should(
"have.value",
data.slug
);
// Verify share modal
cy.get(commonWidgetSelector.shareAppButton).click();
cy.get(commonWidgetSelector.appLink).verifyVisibleElement(
"have.text",
`${host}/applications/`
);
cy.get(commonWidgetSelector.appNameSlugInput).should(
"have.value",
data.slug
);
// Validate all error cases in share modal
verifySlugValidations(commonWidgetSelector.appNameSlugInput);
// Validate all error cases in share modal
verifySlugValidations(commonWidgetSelector.appNameSlugInput);
cy.wait(500);
cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug);
cy.get('[data-cy="app-slug-info-label"]')
.invoke("text")
.then((text) => {
expect(text.trim()).to.eq(
"URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens"
);
});
cy.wait(500);
cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug);
cy.get('[data-cy="app-slug-info-label"]')
.invoke("text")
.then((text) => {
expect(text.trim()).to.eq(
"URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens"
);
});
// Verify successful slug update in share modal
data.slug = `${fake.companyName.toLowerCase()}-app`;
cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug);
cy.get('[data-cy="app-slug-accepted-label"]').verifyVisibleElement(
"have.text",
"Slug accepted!"
);
// Verify successful slug update in share modal
data.slug = `${fake.companyName.toLowerCase()}-app`;
cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug);
cy.get('[data-cy="app-slug-accepted-label"]').verifyVisibleElement(
"have.text",
"Slug accepted!"
);
// Close modal and verify URLs
cy.get(commonWidgetSelector.modalCloseButton).click();
verifyURLs(workspaceId, data.slug, true);
// Close modal and verify URLs
cy.get(commonWidgetSelector.modalCloseButton).click();
verifyURLs(workspaceId, data.slug, true);
// Verify duplicate slug validation in share modal
cy.visit("/my-workspace");
cy.apiCreateApp(data.slug);
cy.openApp("my-workspace");
releaseApp();
cy.get(commonWidgetSelector.shareAppButton).click();
cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug);
cy.get(commonWidgetSelector.appSlugErrorLabel).verifyVisibleElement(
"have.text",
"This app slug is already taken."
);
});
// Verify duplicate slug validation in share modal
cy.visit("/my-workspace");
cy.apiCreateApp(data.slug);
cy.openApp("my-workspace");
releaseApp();
cy.get(commonWidgetSelector.shareAppButton).click();
cy.clearAndType(commonWidgetSelector.appNameSlugInput, data.slug);
cy.get(commonWidgetSelector.appSlugErrorLabel).verifyVisibleElement(
"have.text",
"This app slug is already taken."
);
});
});

View file

@ -20,20 +20,20 @@ import {
describe("Private and Public apps", {
retries: { runMode: 2 },
}, () => {
const data = {};
let data;
beforeEach(() => {
data.appName = `${fake.companyName} P P App`;
data.slug = data.appName.toLowerCase().replace(/\s+/g, "-");
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase();
data.workspaceName = fake.firstName;
data.workspaceSlug = fake.firstName.toLowerCase().replace(/\s+/g, "-");
data = {
appName: `${fake.companyName} P P App`,
slug: `${fake.companyName} P P App`.toLowerCase().replace(/\s+/g, "-"),
firstName: fake.firstName,
email: fake.email.toLowerCase(),
workspaceName: fake.firstName,
workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"),
}
cy.defaultWorkspaceLogin();
cy.skipWalkthrough();
cy.log(data.appName, "text1")
});
it("Verify private and public app share functionality", () => {
@ -85,9 +85,9 @@ describe("Private and Public apps", {
});
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.wait(2000);
cy.loginWithCredentials("dev@tooljet.io", "password");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.appUILogin();
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// Test public access
cy.get(commonSelectors.viewerPageLogo).click();
@ -106,8 +106,8 @@ describe("Private and Public apps", {
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
});
@ -123,30 +123,30 @@ describe("Private and Public apps", {
});
cy.wait(2000);
cy.loginWithCredentials(data.email, "password");
cy.appUILogin(data.email, "password");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible", { timeout: 20000 });
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// Test with private app valid session
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get(commonSelectors.viewerPageLogo).click();
// Test public access
cy.defaultWorkspaceLogin();
cy.wait(1000);
cy.apiMakeAppPublic();
logout();
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// Test with public app with valid session
@ -154,8 +154,8 @@ describe("Private and Public apps", {
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
});
@ -180,8 +180,8 @@ describe("Private and Public apps", {
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// Verify public app with valid session
@ -189,8 +189,8 @@ describe("Private and Public apps", {
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
});
@ -224,8 +224,8 @@ describe("Private and Public apps", {
// Process invitation
onboardUserFromAppLink(data.email, data.slug);
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('[data-cy="viewer-page-logo"]').click();
logout();
@ -269,8 +269,8 @@ describe("Private and Public apps", {
});
onboardUserFromAppLink(data.email, data.slug, data.workspaceName, false);
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
});

View file

@ -114,7 +114,7 @@ describe("App Version", () => {
cy.wait(3000);
// cy.reload();
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible", { timeout: 10000 });
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible", { timeout: 10000 });
// Preview and release verification
cy.openInCurrentTab(commonWidgetSelector.previewButton);

View file

@ -46,7 +46,7 @@ describe("Datasource Manager", () => {
data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsName2 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
const allDataSources = host.includes("8082") ? "All data sources (42)" : "All data sources (44)";
const allDataSources = host.includes("8082") ? "All data sources (43)" : "All data sources (45)";
const allDatabase = host.includes("8082") ? "Databases (18)" : "Databases (20)";
cy.get(commonSelectors.globalDataSourceIcon).click();
@ -214,7 +214,7 @@ describe("Datasource Manager", () => {
cy.get(commonWidgetSelector.sidebarinspector).click();
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
verifyValueOnInspector("table_preview", "7 items ");
verifyValueOnInspector("table_preview", "10 items ");
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".p-2 > .tj-base-btn")
@ -275,7 +275,7 @@ describe("Datasource Manager", () => {
pinInspector();
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
verifyValueOnInspector("table_preview", "7 items ");
verifyValueOnInspector("table_preview", "10 items ");
//scope changing is pending
});

View file

@ -18,10 +18,17 @@ import { roleBasedOnboarding } from "Support/utils/onboarding";
const data = {};
data.groupName = fake.firstName.replaceAll("[^A-Za-z]", "");
data.appName = `${fake.companyName}-App`;
const workspaceName = fake.firstName;
const workspaceSlug = fake.firstName.toLowerCase().replace(/[^A-Za-z]/g, "");
describe("Groups duplication", () => {
beforeEach(() => {
cy.defaultWorkspaceLogin();
cy.apiCreateWorkspace(workspaceName, workspaceSlug);
cy.visit(`${workspaceSlug}`);
cy.apiLogout();
cy.apiLogin();
cy.visit(`${workspaceSlug}`);
groupPermission(
[
"appsCreateCheck",
@ -32,15 +39,18 @@ describe("Groups duplication", () => {
"Admin"
);
cy.apiCreateApp(data.appName);
});
it("Should verify the group duplication feature", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
cy.visit(`${workspaceSlug}`);
roleBasedOnboarding(data.firstName, data.email, "builder");
cy.apiLogout();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit(`${workspaceSlug}`);
navigateToManageGroups();
verifyGroupCardOptions("Admin");
cy.wait(3000);
@ -105,15 +115,19 @@ describe("Groups duplication", () => {
cy.apiLogout();
cy.apiLogin(data.email, "password");
cy.visit("/my-workspace");
cy.visit(`${workspaceSlug}`);
cy.wait(2000);
cy.get(commonSelectors.appCreateButton).should("be.visible");
cy.get(commonSelectors.createNewFolderButton).should("be.visible");
cy.wait(2000);
cy.reload();
viewAppCardOptions(data.appName);
cy.contains("Delete app").should("exist");
cy.get(commonSelectors.workspaceConstantsIcon).should("be.visible");
cy.apiLogout();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit(`${workspaceSlug}`);
navigateToManageGroups();
OpenGroupCardOption(`${data.groupName}_copy`);
cy.get(groupsSelector.deleteGroupOption).click();
@ -121,7 +135,7 @@ describe("Groups duplication", () => {
cy.apiLogout();
cy.apiLogin(data.email, "password");
cy.visit("/my-workspace");
cy.visit(`${workspaceSlug}`);
cy.get(commonSelectors.appCreateButton).should("not.exist");
cy.get(commonSelectors.createNewFolderButton).should("not.exist");
cy.get(commonSelectors.workspaceConstantsIcon).should("not.exist");

View file

@ -124,7 +124,8 @@ describe("Workspace constants", () => {
//verify global constant is resolved in static query url
cy.get('[data-cy="list-query-restapistaticg"]').click();
cy.get('.rest-api-methods-select-element-container .codehinter-container').click();
cy.get('.rest-api-methods-select-element-container .codehinter-container').eq(0).click();
cy.wait(500)
cy.get('.text-secondary').should('have.text', Cypress.env("constants_host"));
//Verify global constant is resolved in static query preview

View file

@ -200,7 +200,7 @@ describe("user invite flow cases", () => {
});
});
it.skip("Should verify the user onboarding with groups", () => {
it("Should verify the user onboarding with groups", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.groupName1 = fake.firstName.replaceAll("[^A-Za-z]", "");

View file

@ -58,7 +58,8 @@ describe("inviteflow edge cases", () => {
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteToast);
logout();
cy.defaultWorkspaceLogin();
cy.apiLogin();
cy.visit(workspaceName);
navigateToManageUsers();
searchUser(data.email);
cy.contains("td", data.email)

View file

@ -8,7 +8,7 @@ import { importText } from "Texts/exportImport";
describe("App creation", () => {
const data = {};
const appFile = "cypress/fixtures/templates/test-app.json";
const appFile = "cypress/fixtures/templates/one_version.json";
beforeEach(() => {
cy.defaultWorkspaceLogin();
@ -200,7 +200,7 @@ describe("App creation", () => {
force: true,
});
cy.get(commonSelectors.importAppTitle).verifyVisibleElement(
cy.get(importSelectors.importAppTitle).verifyVisibleElement(
"have.text",
"Import app"
);
@ -210,7 +210,7 @@ describe("App creation", () => {
);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"have.value",
"test-app"
"one_version"
);
cy.get(commonSelectors.appNameInfoLabel).verifyVisibleElement(
"have.text",
@ -236,7 +236,7 @@ describe("App creation", () => {
});
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"have.value",
"test-app"
"one_version"
);
cy.clearAndType(commonSelectors.appNameInput, data.appName);
cy.get(commonSelectors.cancelButton).click();
@ -247,7 +247,7 @@ describe("App creation", () => {
});
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"have.value",
"test-app"
"one_version"
);
cy.clearAndType(commonSelectors.appNameInput, data.appName);
cy.get(commonSelectors.importAppButton).should("be.enabled").click();

View file

@ -2,7 +2,6 @@ import { fake } from "Fixtures/fake";
import {
createFolder,
deleteFolder,
deleteDownloadsFolder,
navigateToAppEditor,
viewAppCardOptions,
verifyModal,
@ -14,49 +13,38 @@ import {
} from "Support/utils/common";
import {
modifyAndVerifyAppCardIcon,
login,
verifyAppDelete,
} from "Support/utils/dashboard";
import { profileSelector } from "Selectors/profile";
import { profileText } from "Texts/profile";
import { commonSelectors } from "Selectors/common";
import { dashboardSelector } from "Selectors/dashboard";
import { commonText } from "Texts/common";
import { dashboardText } from "Texts/dashboard";
import {
navigateToManageUsers,
logout,
searchUser,
navigateToManageGroups,
} from "Support/utils/common";
import { roleBasedOnboarding } from "Support/utils/onboarding";
import { logout } from "Support/utils/common";
describe("dashboard", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.folderName = `${fake.companyName.toLowerCase()}-folder`;
data.cloneAppName = `cloned-${data.appName}`;
data.updatedFolderName = `new-${data.folderName}`;
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.workspaceName = fake.firstName;
data.workspaceSlug = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", "");
let data = {};
beforeEach(() => {
data = {
appName: `${fake.companyName}-App`,
folderName: `${fake.companyName.toLowerCase()}-folder`,
cloneAppName: `cloned-${fake.companyName}-App`,
updatedFolderName: `new-${fake.companyName.toLowerCase()}-folder`,
workspaceName: fake.firstName,
workspaceSlug: fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""),
};
cy.intercept("GET", "/api/library_apps").as("appLibrary");
cy.intercept("DELETE", "/api/folders/*").as("folderDeleted");
cy.skipWalkthrough();
cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.apiLogout();
cy.apiLogin();
cy.visit(`${data.workspaceSlug}`);
});
it("should verify the elements on empty dashboard", () => {
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=&type=front-end", {
fixture: "intercept/emptyDashboard.json",
}).as("emptyDashboard");
cy.intercept("GET", "/api/folder-apps?searchKey=&type=front-end", {
body: { folders: [] },
}).as("folders");
cy.intercept("GET", "/api/metadata", {
body: {
installed_version: "2.9.2",
@ -64,15 +52,10 @@ describe("dashboard", () => {
},
}).as("version");
cy.defaultWorkspaceLogin();
cy.wait("@emptyDashboard");
cy.wait("@folders");
cy.wait("@version");
cy.get(commonSelectors.homePageLogo).should("be.visible");
cy.get(commonSelectors.workspaceName).verifyVisibleElement(
"have.text",
"My workspace"
data.workspaceName
);
cy.get(commonSelectors.workspaceName).click();
// cy.get(commonSelectors.editRectangleIcon).should("be.visible");
@ -188,12 +171,12 @@ describe("dashboard", () => {
verifyTooltip(dashboardSelector.modeToggle, "Mode");
});
it("Should verify app card elements and app card operations", () => {
it.skip("Should verify app card elements and app card operations", () => {
const customLayout = {
desktop: { top: 100, left: 20 },
mobile: { width: 8, height: 50 },
};
cy.apiLogin();
cy.apiCreateApp(data.appName);
cy.openApp();
cy.apiAddComponentToApp(data.appName, "text1", customLayout);
@ -276,6 +259,7 @@ describe("dashboard", () => {
cancelModal(commonText.cancelButton);
cy.wait(3000);
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.removeFromFolderOption)
@ -296,6 +280,7 @@ describe("dashboard", () => {
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();
@ -312,7 +297,10 @@ describe("dashboard", () => {
cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
cy.wait(3000)
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();
@ -322,6 +310,7 @@ describe("dashboard", () => {
expect(downloadedAppExportFileName).to.contain.string("app");
});
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.deleteAppOption).click();
@ -337,6 +326,7 @@ describe("dashboard", () => {
).verifyVisibleElement("have.text", commonText.modalYesButton);
cancelModal(commonText.cancelButton);
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.deleteAppOption).click();
@ -362,9 +352,6 @@ describe("dashboard", () => {
mobile: { width: 8, height: 50 },
};
cy.skipWalkthrough();
data.appName = `${fake.companyName}-App`;
cy.defaultWorkspaceLogin();
cy.createApp(data.appName);
cy.apiAddComponentToApp(data.appName, "text1", customLayout);
@ -395,12 +382,8 @@ describe("dashboard", () => {
mobile: { width: 8, height: 50 },
};
data.appName = `${fake.companyName}-App`;
cy.defaultWorkspaceLogin();
cy.createApp(data.appName);
cy.apiAddComponentToApp(data.appName, "text1", customLayout);
cy.backToApps();
cy.get(commonSelectors.createNewFolderButton).click();
@ -517,13 +500,4 @@ describe("dashboard", () => {
verifyAppDelete(data.appName);
logout();
});
it("should verify the elements on empty dashboard for end user", () => {
cy.defaultWorkspaceLogin();
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=&type=front-end", {
fixture: "intercept/emptyDashboard.json",
}).as("emptyDashboard")
roleBasedOnboarding(data.firstName, data.email, "end-user");
cy.get(commonSelectors.dashboardAppCreateButton).should("be.disabled");
});
});

View file

@ -0,0 +1,3 @@
{
"id": "bff6583db942c77249ba"
}

View file

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

View file

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

View file

@ -1701,7 +1701,7 @@
]
},
"list_rows": {},
"runOnPageLoad": true
"runOnPageLoad": false
},
"dataSourceId": "f4cf0089-aec2-4713-800e-3560e678220b",
"appVersionId": "b74fcff1-8cf1-40f8-a13d-c2d2a0b1ebf1",
@ -1862,7 +1862,7 @@
"encrypted": false
},
"host": {
"value": "35.202.183.199",
"value": "35.238.9.114",
"encrypted": false
},
"port": {

View file

@ -25,7 +25,10 @@ export const verifySuccessfulSlugUpdate = (workspaceId, slug) => {
"have.text",
"Slug accepted!"
);
cy.get(commonWidgetSelector.appLinkSucessLabel).verifyVisibleElement(
cy.wait(500);
// cy.get(commonWidgetSelector.appLinkSucessLabel).should('be.visible');
cy.get(commonWidgetSelector.appLinkSucessLabel).should(
"have.text",
"Link updated successfully!"
);

View file

@ -32,10 +32,10 @@ export const deleteComponentAndVerify = (widgetName) => {
.last()
.realClick();
});
cy.verifyToastMessage(
`[class=go3958317564]`,
"Component deleted! (Ctrl + Z to undo)"
);
// cy.verifyToastMessage(
// `[class=go3958317564]`,
// "Component deleted! (Ctrl + Z to undo)"
// );
cy.notVisible(commonWidgetSelector.draggableWidget(widgetName));
};

View file

@ -16,9 +16,7 @@ export const navigateToProfile = () => {
export const logout = () => {
cy.get(commonSelectors.settingsIcon).click();
cy.get(commonSelectors.logoutLink).click();
cy.intercept("GET", "/api/metadata").as("publicConfig");
cy.wait("@publicConfig");
cy.wait(500);
cy.wait(1000);
};
export const navigateToManageUsers = () => {
@ -183,10 +181,9 @@ export const manageUsersPagination = (email) => {
export const searchUser = (email) => {
cy.clearAndType(commonSelectors.inputUserSearch, email);
cy.wait(1000)
cy.wait(1000);
};
export const selectAppCardOption = (appName, appCardOption) => {
viewAppCardOptions(appName);
cy.get(appCardOption).should("be.visible").click({ force: true });
@ -221,7 +218,6 @@ export const pinInspector = () => {
}
});
cy.hideTooltip();
};
export const navigateToworkspaceConstants = () => {
@ -243,24 +239,3 @@ export const verifyTooltipDisabled = (selector, message) => {
cy.get(".tooltip-inner").last().should("have.text", message);
});
};
export const deleteAllGroupChips = () => {
cy.get('body').then(($body) => {
if ($body.find('[data-cy="group-chip"]').length > 0) {
cy.get('[data-cy="group-chip"]').then(($groupChip) => {
if ($groupChip.is(':visible')) {
cy.get('[data-cy="group-chip"]').first().click();
cy.get('[data-cy="delete-button"]').click();
cy.get('[data-cy="yes-button"]').click();
cy.wait(2000);
deleteAllGroupChips(); // Recursive call to delete next chip
} else {
cy.log("Group chip is present but not visible, skipping deletion");
}
});
} else {
cy.log("No group chips left to delete");
}
});
}

View file

@ -34,7 +34,7 @@ export const verifyValue = (node, type, children, index = 0) => {
};
export const deleteComponentFromInspector = (node) => {
cy.get('[data-cy="inspector-node-components"] > .node-key').click();
cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').click();
cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').last().click();
};
export const verifyfunctions = (node, type, index = 0) => {

View file

@ -646,7 +646,7 @@ export const createGroupAddAppAndUserToGroup = (groupName, email) => {
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/v2/group_permissions`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions`,
headers: headers,
body: {
name: groupName,
@ -658,14 +658,14 @@ export const createGroupAddAppAndUserToGroup = (groupName, email) => {
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/v2/group_permissions/granular-permissions`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/granular-permissions`,
headers: headers,
body: {
name: "Apps",
type: "app",
groupId: groupId,
isAll: false,
createAppsPermissionsObject: {
createResourcePermissionObject: {
canEdit: true,
canView: false,
hideFromDashboard: false,
@ -676,19 +676,22 @@ export const createGroupAddAppAndUserToGroup = (groupName, email) => {
],
},
},
}).then((response) => {
expect(response.status).to.equal(201);
});
cy.wait(2000);
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from users where email='${email}';`,
}).then((resp) => {
const userId = resp.rows[0].id;
cy.log(userId);
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/v2/group_permissions/group-user`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/users`,
headers: headers,
body: {
userIds: [userId],
@ -720,7 +723,7 @@ export const OpenGroupCardOption = (groupName) => {
export const duplicateMultipleGroups = (groupNames) => {
groupNames.forEach((groupName) => {
OpenGroupCardOption(groupName);
cy.wait(3000);
cy.wait(2000);
cy.get(commonSelectors.duplicateOption).click(); // Click on the duplicate option
cy.get(commonSelectors.confirmDuplicateButton).click(); // Confirm duplication if needed
});

View file

@ -18,7 +18,7 @@ export const generalSettings = () => {
cy.get(ssoSelector.workspaceLoginPage.defaultSSO).click();
cy.get(ssoSelector.defaultGoogle).verifyVisibleElement("have.text", "Google");
cy.get(ssoSelector.defaultGithub).verifyVisibleElement("have.text", "Github");
cy.get(ssoSelector.defaultGithub).verifyVisibleElement("have.text", "Git");
cy.clearAndType(ssoSelector.allowedDomainInput, ssoText.allowedDomain);
cy.get(ssoSelector.saveButton).click();
@ -416,7 +416,7 @@ export const resetDomain = () => {
cy.request(
{
method: "PATCH",
url: `${Cypress.env("server_host")}/api/organizations`,
url: `${Cypress.env("server_host")}/api/login-configs/organization-general`,
headers: {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,

View file

@ -57,7 +57,7 @@ export const setHomePage = (pageName) => {
export const addNewPage = (pageName) => {
cy.get(multipageSelector.addPageIcon).click();
cy.get(".col-12 > .form-control").type(`{selectAll}{backspace}${pageName}`);
cy.get('[role="button"] > div > .form-control').type(`{selectAll}{backspace}${pageName}`);
cy.get(multipageSelector.addPageIcon).click();
cy.get(`[data-cy="pages-name-${pageName.toLowerCase()}"]`).click();
};

View file

@ -47,6 +47,8 @@ export const waitForQueryAction = (action) => {
export const chainQuery = (currentQuery, trigger) => {
cy.get(`[data-cy="list-query-${currentQuery}"]`).click();
cy.wait(1000);
cy.get('[data-cy="query-tab-settings"]').click();
selectEvent("Query Success", "Run Query");
cy.get('[data-cy="query-selection-field"]')
.click()
@ -55,8 +57,16 @@ export const chainQuery = (currentQuery, trigger) => {
};
export const addSuccessNotification = (notification) => {
changeQueryToggles("notification-on-success");
cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror(
notification
);
cy.get('[data-cy="query-tab-settings"]').click();
cy.get('body').then(($body) => {
if (!$body.find('[data-cy="success-message-input-field"]').is(':visible')) {
changeQueryToggles("notification-on-success");
// cy.get('[data-cy="success-message-input-field"]').then(($input) => {
// cy.wrap($input).clearAndTypeOnCodeMirror(notification);
// });
}
});
cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror(notification);
cy.get('[data-cy="query-tab-setup"]').click();
cy.wait(300);
};

View file

@ -0,0 +1,97 @@
export const createAndRunRestAPIQuery = (
queryName,
dsName,
method = "GET",
url = "",
headersList = [],
bodyList = [],
jsonBody = null,
run = true,
urlSuffix = ""
) => {
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
};
cy.request({
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,
};
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

@ -70,12 +70,13 @@ COPY --from=builder /app/frontend/build ./app/frontend/build
# copy server build
COPY --from=builder /app/server/package.json ./app/server/package.json
COPY --from=builder /app/server/.version ./app/server/.version
COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh
COPY --from=builder /app/server/node_modules ./app/server/node_modules
COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
COPY ./docker/ce-entrypoint.sh ./app/server/entrypoint.sh
WORKDIR /app
# ENV defaults

View file

@ -99,12 +99,13 @@ COPY --from=builder /app/frontend/build ./app/frontend/build
COPY --from=builder /app/server/package.json ./app/server/package.json
COPY --from=builder /app/server/.version ./app/server/.version
COPY --from=builder /app/server/ee/keys ./app/server/ee/keys
COPY --from=builder /app/server/entrypoint.sh ./app/server/entrypoint.sh
COPY --from=builder /app/server/node_modules ./app/server/node_modules
COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
WORKDIR /app
# ENV defaults

View file

@ -0,0 +1,2 @@
First Name,Last Name,Email,User Role,Group
test,user,test@gmail.com,"Assign each user a role: Admin, Builder or End User. User role value should be exact same","For multiple groups separate using pipe (|) operator e.g. Groups1|Group2 or leave blank if no group assign"
1 First Name Last Name Email User Role Group
2 test user test@gmail.com Assign each user a role: Admin, Builder or End User. User role value should be exact same For multiple groups separate using pipe (|) operator e.g. Groups1|Group2 or leave blank if no group assign

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

View file

@ -41,6 +41,8 @@ import {
import { shallow } from 'zustand/shallow';
import useStore from '@/AppBuilder/_stores/store';
import { checkIfToolJetCloud } from '@/_helpers/utils';
import { BasicPlanMigrationBanner } from '@/HomePage/BasicPlanMigrationBanner/BasicPlanMigrationBanner';
import { licenseService } from '@/_services';
const AppWrapper = (props) => {
const { isAppDarkMode } = useAppDarkMode();
@ -68,12 +70,24 @@ class AppComponent extends React.Component {
currentUser: null,
fetchedMetadata: false,
darkMode: localStorage.getItem('darkMode') === 'true',
showBanner: false,
// isEditorOrViewer: '',
};
}
updateSidebarNAV = (val) => {
this.setState({ sidebarNav: val });
};
updateMargin() {
const isAdmin = authenticationService?.currentSessionValue?.admin;
const isBuilder = authenticationService?.currentSessionValue?.is_builder;
const setupDate = authenticationService?.currentSessionValue?.consultation_banner_date;
const showBannerCondition =
(isAdmin || isBuilder) && setupDate && this.isExistingPlanUser(setupDate) && this.state.showBanner;
const marginValue = showBannerCondition ? '25' : '0';
const marginValueLayout = showBannerCondition ? '35' : '0';
document.documentElement.style.setProperty('--dynamic-margin', `${marginValue}px`);
document.documentElement.style.setProperty('--dynamic-margin-2', `${marginValueLayout}px`);
}
fetchMetadata = () => {
tooljetService.fetchMetaData().then((data) => {
@ -89,11 +103,15 @@ class AppComponent extends React.Component {
});
};
componentDidMount() {
async componentDidMount() {
setFaviconAndTitle();
authorizeWorkspace();
this.fetchMetadata();
setInterval(this.fetchMetadata, 1000 * 60 * 60 * 1);
this.updateMargin(); // Set initial margin
const featureAccess = await licenseService.getFeatureAccess();
const isBasicPlan = !featureAccess?.licenseStatus?.isLicenseValid || featureAccess?.licenseStatus?.isExpired;
this.setState({ showBanner: isBasicPlan });
}
// check if its getting routed from editor
checkPreviousRoute = (route) => {
@ -114,6 +132,8 @@ class AppComponent extends React.Component {
// Reload the page for clearing already set intervals
window.location.reload();
}
// Update margin when showBanner changes
this.updateMargin();
}
switchDarkMode = (newMode) => {
@ -130,8 +150,14 @@ class AppComponent extends React.Component {
}
return '';
};
closeBasicPlanMigrationBanner = () => {
this.setState({ showBanner: false });
};
isExistingPlanUser = (date) => {
return new Date(date) < new Date('2025-04-24'); //show banner if user created before 2 april (24 for testing)
};
render() {
const { updateAvailable, darkMode, isEditorOrViewer } = this.state;
const { updateAvailable, darkMode, isEditorOrViewer, showBanner } = this.state;
const mergedProps = {
...this.props,
switchDarkMode: this.switchDarkMode,
@ -156,220 +182,236 @@ class AppComponent extends React.Component {
}
const { sidebarNav } = this.state;
const { updateSidebarNAV } = this;
const isApplicationsPath = window.location.pathname.includes('/applications/');
const isAdmin = authenticationService?.currentSessionValue?.admin;
const isBuilder = authenticationService?.currentSessionValue?.is_builder;
const setupDate = authenticationService?.currentSessionValue?.consultation_banner_date;
return (
<>
<div
className={cx('main-wrapper', {
'theme-dark dark-theme': !this.isEditorOrViewerFromPath() && darkMode,
})}
data-cy="main-wrapper"
>
{updateAvailable && (
<div className="alert alert-info alert-dismissible" role="alert">
<h3 className="mb-1">Update available</h3>
<p>A new version of ToolJet has been released.</p>
<div className="btn-list">
<a
href="https://docs.tooljet.io/docs/setup/updating"
target="_blank"
className="btn btn-info"
rel="noreferrer"
>
Read release notes & update
</a>
<a
onClick={() => {
tooljetService.skipVersion();
this.setState({ updateAvailable: false });
}}
className="btn"
>
Skip this version
</a>
<div className={!isApplicationsPath && (isAdmin || isBuilder) ? 'banner-layout-wrapper' : ''}>
{!isApplicationsPath &&
(isAdmin || isBuilder) &&
showBanner &&
setupDate &&
this.isExistingPlanUser(setupDate) && (
<BasicPlanMigrationBanner darkMode={darkMode} closeBanner={this.closeBasicPlanMigrationBanner} />
)}
<div
className={cx('main-wrapper', {
'theme-dark dark-theme': !this.isEditorOrViewerFromPath() && darkMode,
})}
data-cy="main-wrapper"
>
{updateAvailable && (
<div className="alert alert-info alert-dismissible" role="alert">
<h3 className="mb-1">Update available</h3>
<p>A new version of ToolJet has been released.</p>
<div className="btn-list">
<a
href="https://docs.tooljet.io/docs/setup/updating"
target="_blank"
className="btn btn-info"
rel="noreferrer"
>
Read release notes & update
</a>
<a
onClick={() => {
tooljetService.skipVersion();
this.setState({ updateAvailable: false });
}}
className="btn"
>
Skip this version
</a>
</div>
</div>
</div>
)}
<BreadCrumbContext.Provider value={{ sidebarNav, updateSidebarNAV }}>
<Routes>
{onboarding(this.props)}
{auth(this.props)}
<Route path="/sso/:origin/:configId" exact element={<Oauth {...this.props} />} />
<Route path="/sso/:origin" exact element={<Oauth {...this.props} />} />
<Route
exact
path="/:workspaceId/apps/:slug/:pageHandle?/*"
element={
<AppsRoute componentType="editor">
<AppLoader switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</AppsRoute>
}
/>
<Route
exact
path="/:workspaceId/workspace-constants"
element={
<PrivateRoute>
<WorkspaceConstants switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route
exact
path="/applications/:slug/:pageHandle?"
element={
<AppsRoute componentType="viewer">
<Viewer switchDarkMode={this.switchDarkMode} darkMode={this.props.isAppDarkMode} />
</AppsRoute>
}
/>
<Route
exact
path="/applications/:slug/versions/:versionId/environments/:environmentId/:pageHandle?"
element={
<AppsRoute componentType="viewer">
<Viewer switchDarkMode={this.switchDarkMode} darkMode={this.props.isAppDarkMode} />
</AppsRoute>
}
/>
<Route
exact
path="/oauth2/authorize"
element={
<PrivateRoute>
<Authorize switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
{window.public_config?.ENABLE_WORKFLOWS_FEATURE === 'true' && (
)}
<BreadCrumbContext.Provider value={{ sidebarNav, updateSidebarNAV }}>
<Routes>
{onboarding(this.props)}
{auth(this.props)}
<Route path="/sso/:origin/:configId" exact element={<Oauth {...this.props} />} />
<Route path="/sso/:origin" exact element={<Oauth {...this.props} />} />
<Route
exact
path="/:workspaceId/workflows/*"
path="/:workspaceId/apps/:slug/:pageHandle?/*"
element={
<AdminRoute {...this.props}>
<Workflows switchDarkMode={this.switchDarkMode} darkMode={this.darkMode} />
</AdminRoute>
<AppsRoute componentType="editor">
<AppLoader switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</AppsRoute>
}
/>
)}
<Route path="/:workspaceId/workspace-settings/*" element={<WorkspaceSettings {...mergedProps} />}></Route>
<Route path="settings/*" element={<InstanceSettings {...this.props} />}></Route>
<Route path="/:workspaceId/settings/*" element={<Settings {...this.props} />}></Route>
{getAuditLogsRoutes(this.props)}
<Route
exact
path="/:workspaceId/profile-settings"
element={
<PrivateRoute>
<SettingsPage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
{getDataSourcesRoutes(mergedProps)}
<Route
exact
path="/applications/:id/versions/:versionId/:pageHandle?"
element={
<PrivateRoute>
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route
exact
path="/applications/:slug/:pageHandle?"
element={
<PrivateRoute>
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route
exact
path="/:workspaceId/database"
element={
<PrivateRoute>
<TooljetDatabase switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
{this.state.tooljetVersion && !checkIfToolJetCloud(this.state.tooljetVersion) && (
<Route
exact
path="/integrations"
path="/:workspaceId/workspace-constants"
element={
<AdminRoute {...this.props}>
<MarketplacePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</AdminRoute>
<PrivateRoute>
<WorkspaceConstants switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
>
<Route path="installed" element={<InstalledPlugins />} />
<Route path="marketplace" element={<MarketplacePlugins />} />/
</Route>
)}
<Route exact path="/" element={<Navigate to="/:workspaceId" />} />
<Route
exact
path="/error/:errorType"
element={<ErrorPage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />}
/>
<Route
exact
path="/app-url-archived"
element={
<SwitchWorkspacePage
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
archived={true}
isAppUrl={true}
/>
<Route
exact
path="/applications/:slug/:pageHandle?"
element={
<AppsRoute componentType="viewer">
<Viewer switchDarkMode={this.switchDarkMode} darkMode={this.props.isAppDarkMode} />
</AppsRoute>
}
/>
<Route
exact
path="/applications/:slug/versions/:versionId/environments/:environmentId/:pageHandle?"
element={
<AppsRoute componentType="viewer">
<Viewer switchDarkMode={this.switchDarkMode} darkMode={this.props.isAppDarkMode} />
</AppsRoute>
}
/>
<Route
exact
path="/oauth2/authorize"
element={
<PrivateRoute>
<Authorize switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
{window.public_config?.ENABLE_WORKFLOWS_FEATURE === 'true' && (
<Route
exact
path="/:workspaceId/workflows/*"
element={
<AdminRoute {...this.props}>
<Workflows switchDarkMode={this.switchDarkMode} darkMode={this.darkMode} />
</AdminRoute>
}
/>
}
/>
<Route
exact
path="/switch-workspace"
element={
<SwitchWorkspaceRoute>
<SwitchWorkspacePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</SwitchWorkspaceRoute>
}
/>
<Route
exact
path="/switch-workspace-archived"
element={
<SwitchWorkspaceRoute>
<SwitchWorkspacePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} archived={true} />
</SwitchWorkspaceRoute>
}
/>
<Route
exact
path="/:workspaceId"
element={
<PrivateRoute>
<HomePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} appType={'front-end'} />
</PrivateRoute>
}
/>
<Route
path="*"
render={() => {
if (authenticationService?.currentSessionValue?.current_organization_id) {
return <Navigate to="/:workspaceId" />;
}
return <Navigate to="/login" />;
}}
/>
</Routes>
</BreadCrumbContext.Provider>
<div id="modal-div"></div>
</div>
)}
<Route
path="/:workspaceId/workspace-settings/*"
element={<WorkspaceSettings {...mergedProps} />}
></Route>
<Route path="settings/*" element={<InstanceSettings {...this.props} />}></Route>
<Route path="/:workspaceId/settings/*" element={<Settings {...this.props} />}></Route>
<Toast toastOptions={toastOptions} />
{getAuditLogsRoutes(this.props)}
<Route
exact
path="/:workspaceId/profile-settings"
element={
<PrivateRoute>
<SettingsPage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
{getDataSourcesRoutes(mergedProps)}
<Route
exact
path="/applications/:id/versions/:versionId/:pageHandle?"
element={
<PrivateRoute>
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route
exact
path="/applications/:slug/:pageHandle?"
element={
<PrivateRoute>
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route
exact
path="/:workspaceId/database"
element={
<PrivateRoute>
<TooljetDatabase switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
{this.state.tooljetVersion && !checkIfToolJetCloud(this.state.tooljetVersion) && (
<Route
exact
path="/integrations"
element={
<AdminRoute {...this.props}>
<MarketplacePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</AdminRoute>
}
>
<Route path="installed" element={<InstalledPlugins />} />
<Route path="marketplace" element={<MarketplacePlugins />} />/
</Route>
)}
<Route exact path="/" element={<Navigate to="/:workspaceId" />} />
<Route
exact
path="/error/:errorType"
element={<ErrorPage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />}
/>
<Route
exact
path="/app-url-archived"
element={
<SwitchWorkspacePage
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
archived={true}
isAppUrl={true}
/>
}
/>
<Route
exact
path="/switch-workspace"
element={
<SwitchWorkspaceRoute>
<SwitchWorkspacePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</SwitchWorkspaceRoute>
}
/>
<Route
exact
path="/switch-workspace-archived"
element={
<SwitchWorkspaceRoute>
<SwitchWorkspacePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} archived={true} />
</SwitchWorkspaceRoute>
}
/>
<Route
exact
path="/:workspaceId"
element={
<PrivateRoute>
<HomePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} appType={'front-end'} />
</PrivateRoute>
}
/>
<Route
path="*"
render={() => {
if (authenticationService?.currentSessionValue?.current_organization_id) {
return <Navigate to="/:workspaceId" />;
}
return <Navigate to="/login" />;
}}
/>
</Routes>
</BreadCrumbContext.Provider>
<div id="modal-div"></div>
</div>
<Toast toastOptions={toastOptions} />
</div>
</>
);
}

View file

@ -15,7 +15,7 @@ const CreateVersionModal = ({
canCommit,
orgGit,
fetchingOrgGit,
handleCommitOnVersionCreation = () => {},
handleCommitOnVersionCreation = () => { },
}) => {
const [isCreatingVersion, setIsCreatingVersion] = useState(false);
const [versionName, setVersionName] = useState('');
@ -94,12 +94,15 @@ const CreateVersionModal = ({
handleCommitOnVersionCreation(data);
})
.catch((error) => {
console.log({ error });
toast.error(error);
});
},
(error) => {
toast.error(error?.error);
if (error?.data?.code === "23505") {
toast.error("Version name already exists.");
} else {
toast.error(error?.error);
}
setIsCreatingVersion(false);
}
);

View file

@ -150,11 +150,7 @@ export const CustomSelect = ({ currentEnvironment, onSelectVersion, ...props })
{/* When we merge this code to EE update the defaultAppEnvironments object with rest of default environments (then delete this comment)*/}
<ConfirmDialog
show={deleteVersion.showModal}
message={`${
defaultAppEnvironments.length > 1
? 'Deleting a version will permanently remove it from all environments.'
: ''
}Are you sure you want to delete this version - ${decodeEntities(deleteVersion.versionName)}?`}
message={`Are you sure you want to delete this version - ${decodeEntities(deleteVersion.versionName)}?`}
onConfirm={() => deleteAppVersion(deleteVersion.versionId, deleteVersion.versionName)}
onCancel={resetDeleteModal}
/>

View file

@ -1,63 +0,0 @@
import React, { useState } from 'react';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import { shallow } from 'zustand/shallow';
import { ToolTip } from '@/_components/ToolTip';
import PromoteConfirmationModal from './PromoteConfirmationModal';
import useStore from '@/AppBuilder/_stores/store';
const PromoteVersionButton = () => {
const [promoteModalData, setPromoteModalData] = useState(null);
const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = useStore(
(state) => ({
isSaving: state.app.isSaving,
editingVersion: state.currentVersionId,
selectedEnvironment: state.selectedEnvironment,
environments: state.environments,
appVersionEnvironment: state.appVersionEnvironment,
}),
shallow
);
const shouldDisablePromote = isSaving || selectedEnvironment?.priority < appVersionEnvironment?.priority;
const handlePromote = () => {
const curentEnvIndex = environments.findIndex((env) => env.id === appVersionEnvironment.id);
setPromoteModalData({
current: appVersionEnvironment,
target: environments[curentEnvIndex + 1],
});
};
return (
<>
<ButtonSolid
variant="primary"
onClick={handlePromote}
size="md"
disabled={shouldDisablePromote}
data-cy="promote-button"
>
<ToolTip message="Promote this version to the next environment" placement="bottom" show={!shouldDisablePromote}>
<div style={{ fontSize: '14px' }}>Promote </div>
</ToolTip>
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.276332 7.02113C0.103827 7.23676 0.138788 7.55141 0.354419 7.72391C0.57005 7.89642 0.884696 7.86146 1.0572 7.64583L3.72387 4.31249C3.86996 4.12988 3.86996 3.87041 3.72387 3.6878L1.0572 0.354464C0.884696 0.138833 0.57005 0.103872 0.354419 0.276377C0.138788 0.448881 0.103827 0.763528 0.276332 0.979158L2.69312 4.00014L0.276332 7.02113ZM4.27633 7.02113C4.10383 7.23676 4.13879 7.55141 4.35442 7.72391C4.57005 7.89642 4.8847 7.86146 5.0572 7.64583L7.72387 4.31249C7.86996 4.12988 7.86996 3.87041 7.72387 3.6878L5.0572 0.354463C4.8847 0.138832 4.57005 0.103871 4.35442 0.276377C4.13879 0.448881 4.10383 0.763527 4.27633 0.979158L6.69312 4.00014L4.27633 7.02113Z"
fill={shouldDisablePromote ? '#C1C8CD' : '#FDFDFE'}
/>
</svg>
</ButtonSolid>
<PromoteConfirmationModal
data={promoteModalData}
editingVersion={editingVersion}
onClose={() => setPromoteModalData(null)}
fetchEnvironments={() => {}}
/>
</>
);
};
export default PromoteVersionButton;

View file

@ -31,6 +31,7 @@ export const PageGroupMenu = ({ darkMode, isLicensed, disabled }) => {
if (!isLicensed) {
return (
<button
data-cy="add-page-button"
disabled={disabled}
onClick={(event) => {
if (disabled) return;

View file

@ -183,7 +183,7 @@ export const PageMenuItem = withRouter(
) : (
<>
{' '}
<div className="left">
<div className="left" data-cy={`pages-name-${page.name.toLowerCase()}`}>
{icon()}
<OverflowTooltip childrenClassName="page-name" style={{ ...computedStyles?.text }}>
{page.name}

View file

@ -36,16 +36,23 @@ function DataSourcePicker({ darkMode }) {
(gds) => gds.type === DATA_SOURCE_TYPE.STATIC
);
//StaicDataSources DIDNT HAVE ID
const updatedStaticDataSources = staticDataSources.map((source) => {
// Find a matching object from staticDataSourcesFullObject based on the 'kind' field
const matchingObject = staticDataSourcesFullObject?.find((gds) => gds.kind === source.kind);
const updatedStaticDataSources = staticDataSources
.filter((source) => {
if (source.kind === 'workflows') {
return staticDataSourcesFullObject?.some((gds) => gds.kind === 'workflows');
}
return true;
})
.map((source) => {
// Find a matching object from staticDataSourcesFullObject based on the 'kind' field
const matchingObject = staticDataSourcesFullObject?.find((gds) => gds.kind === source.kind);
// Replace the 'id' with the one from the matching object, or keep the existing one if no match
return {
...source,
id: matchingObject?.id || source.id,
};
});
// Replace the 'id' with the one from the matching object, or keep the existing one if no match
return {
...source,
id: matchingObject?.id || source.id,
};
});
const docLink = 'sampledb.com';

View file

@ -274,7 +274,7 @@ export const BaseQueryManagerBody = ({ darkMode, activeTab, renderCopilot = () =
const renderChangeDataSource = () => {
const selectableDataSources = [...dataSources, ...globalDataSources, !!sampleDataSource && sampleDataSource]
.filter(Boolean)
.filter((ds) => ds.kind === selectedQuery?.kind);
.filter((ds) => ds.kind === selectedQuery?.kind && ds.type !== DATA_SOURCE_TYPE.STATIC);
if (isEmpty(selectableDataSources)) {
return '';
}

View file

@ -110,6 +110,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTa
(tab.condition === undefined || tab.condition) && (
<p
key={tab.id}
data-cy={`query-tab-${tab.label.toLowerCase()}`}
className="m-0 d-flex align-items-center h-100"
onClick={() => setActiveTab(tab.id)}
style={{
@ -151,8 +152,8 @@ const NameInput = ({ onInput, value, darkMode, isDiabled, selectedQuery }) => {
const hasPermissions =
selectedDataSourceScope === 'global'
? canUpdateDataSource(selectedQuery?.data_source_id) ||
canReadDataSource(selectedQuery?.data_source_id) ||
canDeleteDataSource()
canReadDataSource(selectedQuery?.data_source_id) ||
canDeleteDataSource()
: true;
const inputRef = useRef();
@ -274,8 +275,8 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => {
const hasPermissions =
selectedDataSource?.scope === 'global' && selectedDataSource?.type !== DATA_SOURCE_TYPE.SAMPLE
? canUpdateDataSource(selectedQuery?.data_source_id) ||
canReadDataSource(selectedQuery?.data_source_id) ||
canDeleteDataSource()
canReadDataSource(selectedQuery?.data_source_id) ||
canDeleteDataSource()
: true;
const isPreviewQueryLoading = useStore((state) => state.queryPanel.isPreviewQueryLoading);
const { t } = useTranslation();

View file

@ -170,6 +170,7 @@ export const QueryPanel = ({ darkMode }) => {
}}
>
<button
data-cy="query-manager-toggle-button"
className="d-flex items-center justify-start mb-0 font-weight-500 text-dark select-none query-manager-toggle-button gap-1"
onClick={toggleQueryEditor}
>

View file

@ -16,7 +16,7 @@ const TooljetBanner = ({ isDarkMode }) => {
<div
className="powered-with-tj"
onClick={() => {
const url = `https://tooljet.com/?utm_source=powered_by_banner&utm_medium=${instanceId}&utm_campaign=self_hosted`;
const url = `https://tooljet.com`;
window.open(url, '_blank');
}}
>

View file

@ -19,9 +19,10 @@ export const formConfig = {
accessorKey: 'text',
styles: ['fontWeight', 'textSize', 'textColor'],
defaultValue: {
text: 'Form',
text: 'Form title',
textSize: 16,
textColor: '#000',
fontWeight: 'bold',
},
},
{
@ -29,8 +30,9 @@ export const formConfig = {
slotName: 'footer',
layout: {
top: 12,
left: 32,
left: 29,
height: 36,
width: 13,
},
properties: ['text'],
defaultValue: {
@ -42,9 +44,9 @@ export const formConfig = {
componentName: 'TextInput',
layout: {
top: 20,
left: 5,
left: 1,
height: 40,
width: 31,
width: 41,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
@ -62,9 +64,9 @@ export const formConfig = {
componentName: 'NumberInput',
layout: {
top: 80,
left: 5,
left: 1,
height: 40,
width: 31,
width: 41,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
@ -78,26 +80,6 @@ export const formConfig = {
padding: 'default',
},
},
{
componentName: 'TextInput',
layout: {
top: 140,
left: 5,
height: 40,
width: 31,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
placeholder: 'Tomy',
label: 'Pet name',
width: '{{60}}',
alignment: 'side',
direction: 'left',
auto: '{{false}}',
padding: 'default',
},
},
],
component: 'Form',
others: {
@ -289,6 +271,8 @@ export const formConfig = {
backgroundColor: { value: '#fff' },
borderRadius: { value: '0' },
borderColor: { value: '#fff' },
headerBackgroundColor: { value: '#fff' },
footerBackgroundColor: { value: '#fff' },
},
},
};

View file

@ -90,9 +90,9 @@ export const Modal = function Modal({
const onHideSideEffects = () => {
const canvasElement = document.querySelector('.page-container.canvas-container');
const realCanvasEl = document.getElementsByClassName('real-canvas')[0];
const allModalContainers = realCanvasEl.querySelectorAll('.modal');
const modalContainer = allModalContainers[allModalContainers.length - 1];
const hasManyModalsOpen = allModalContainers.length > 1;
const allModalContainers = realCanvasEl?.querySelectorAll('.modal');
const modalContainer = allModalContainers?.[allModalContainers.length - 1];
const hasManyModalsOpen = allModalContainers?.length > 1;
if (canvasElement && realCanvasEl && modalContainer) {
modalContainer.style.height = ``;

View file

@ -267,6 +267,11 @@ export const DropdownV2 = ({
setExposedVariable('isMandatory', isMandatory);
}, [isMandatory]);
useEffect(() => {
if (isInitialRender.current) return;
setExposedVariable('value', currentValue);
}, [currentValue]);
useEffect(() => {
if (isInitialRender.current) return;
const validationStatus = validate(currentValue);

View file

@ -52,7 +52,14 @@ export const getInputBackgroundColor = ({ fieldBackgroundColor, darkMode, isLoad
};
export const highlightText = (text = '', highlight) => {
const parts = text?.split(new RegExp(`(${highlight})`, 'gi'));
// Escape special regex characters in the highlight string
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
const safeHighlight = highlight ? escapeRegExp(highlight) : '';
const parts = text?.split(new RegExp(`(${safeHighlight})`, 'gi'));
return (
<span>
{parts.map((part, index) =>

View file

@ -6,7 +6,11 @@ import './multiselectV2.scss';
const CustomValueContainer = ({ children, ...props }) => {
const selectProps = props.selectProps;
const values = Array.isArray(selectProps?.value) && selectProps?.value?.map((option) => option.label);
const values =
Array.isArray(selectProps?.value) &&
selectProps?.value
?.filter((option) => option.value !== 'multiselect-custom-menulist-select-all') //Remove the Select all option if selected
?.map((option) => option.label);
const isAllOptionsSelected = selectProps?.value.length === selectProps.options.length;
const valueContainerWidth = selectProps?.containerRef?.current?.offsetWidth;
// eslint-disable-next-line import/namespace

View file

@ -146,7 +146,7 @@ export const MultiselectV2 = ({
if (action.option?.value === SELECT_ALL) {
// Case 1 - If select all is selected
if (action.action === 'select-option') {
setInputValue(modifiedSelectOptions);
setInputValue(selectOptions);
} else {
setInputValue([]);
}
@ -155,7 +155,7 @@ export const MultiselectV2 = ({
setInputValue(items.filter((item) => item.value !== SELECT_ALL));
} else if (selectOptions?.length === items?.length) {
// Case 3 - If all options are selected except select all
setInputValue(modifiedSelectOptions);
setInputValue(selectOptions);
} else {
// Case 4 - Normal selection
setInputValue(items);
@ -503,7 +503,7 @@ export const MultiselectV2 = ({
ref={selectRef}
menuId={id}
isDisabled={isMultiSelectDisabled}
value={selected}
value={selectOptions?.length === selected?.length ? modifiedSelectOptions : selected}
onChange={onChangeHandler}
options={modifiedSelectOptions}
styles={customStyles}

View file

@ -16,7 +16,7 @@ const TooljetBanner = ({ isDarkMode }) => {
<div
className="powered-with-tj"
onClick={() => {
const url = `https://tooljet.com/?utm_source=powered_by_banner&utm_medium=${instanceId}&utm_campaign=self_hosted`;
const url = `https://tooljet.com`;
window.open(url, '_blank');
}}
>

View file

@ -19,9 +19,10 @@ export const formConfig = {
accessorKey: 'text',
styles: ['fontWeight', 'textSize', 'textColor'],
defaultValue: {
text: 'Form',
text: 'Form title',
textSize: 16,
textColor: '#000',
fontWeight: 'bold',
},
},
{
@ -29,8 +30,9 @@ export const formConfig = {
slotName: 'footer',
layout: {
top: 12,
left: 32,
left: 29,
height: 36,
width: 13,
},
properties: ['text'],
defaultValue: {
@ -42,9 +44,9 @@ export const formConfig = {
componentName: 'TextInput',
layout: {
top: 20,
left: 5,
left: 1,
height: 40,
width: 31,
width: 41,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
@ -62,9 +64,9 @@ export const formConfig = {
componentName: 'NumberInput',
layout: {
top: 80,
left: 5,
left: 1,
height: 40,
width: 31,
width: 41,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
@ -78,26 +80,6 @@ export const formConfig = {
padding: 'default',
},
},
{
componentName: 'TextInput',
layout: {
top: 140,
left: 5,
height: 40,
width: 31,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
placeholder: 'Tomy',
label: 'Pet name',
width: '{{60}}',
alignment: 'side',
direction: 'left',
auto: '{{false}}',
padding: 'default',
},
},
],
component: 'Form',
others: {
@ -289,6 +271,8 @@ export const formConfig = {
backgroundColor: { value: '#fff' },
borderRadius: { value: '0' },
borderColor: { value: '#fff' },
headerBackgroundColor: { value: '#fff' },
footerBackgroundColor: { value: '#fff' },
},
},
};

View file

@ -0,0 +1,34 @@
import React, { useState } from 'react';
import './BasicPlanMigrationBanner.scss';
import CloseIcon from '@/_ui/Icon/bulkIcons/CloseIcon';
export const BasicPlanMigrationBanner = ({ closeBanner, darkMode }) => {
return (
<div className={`${darkMode ? 'theme-dark dark-theme' : ''} basic-plan-migration-banner`}>
<div style={{ marginLeft: 'auto' }}>
<p className="banner-text">
We&apos;ve updated your plan limits to align with our{' '}
<a href="https://www.tooljet.ai/pricing" className="banner-link" target="_blank" rel="noopener noreferrer">
new pricing.
</a>{' '}
For help in retrieving data or any inquiries,{' '}
<a
href="https://docs.tooljet.ai/docs/tj-setup/licensing/self-hosted/"
className="banner-link"
target="_blank"
rel="noopener noreferrer"
>
read docs
</a>{' '}
or{' '}
<a href="mailto:hello@tooljet.com" className="banner-link" target="_blank" rel="noopener noreferrer">
contact us
</a>
</p>
</div>
<div onClick={closeBanner} type="button">
<CloseIcon width="12" fill="#3E63DD" opacity="1" secondaryFill="#ffffff" />
</div>
</div>
);
};

View file

@ -0,0 +1,44 @@
.basic-plan-migration-banner {
background-color: var(--background-accent-weak);
padding: 12px 16px;
width: 100%;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
.banner-text {
color: var(--text-accent, #4368E3);
font-size: 12px;
line-height: 18px;
font-weight: 400;
}
.banner-text{
margin-bottom: 0;
}
.banner-link {
color: var(--primary-brand);
text-decoration: underline;
font-weight: 500;
&:hover {
color: var(--indigo-100);
}
}
div[type="button"] {
margin-left: auto;
}
}
// banner css changes
.banner-layout-wrapper {
height: 100vh !important;
overflow: hidden;
/* prevents scrolling beyond this height */
position: relative;
background-color: var(--background-accent-weak);
/* content background */
}

View file

@ -21,6 +21,7 @@ export const BlankPage = function BlankPage({
viewTemplateLibraryModal,
appType,
canCreateApp,
workflowsLimit,
}) {
const { t } = useTranslation();
const whiteLabelText = retrieveWhiteLabelText();
@ -43,6 +44,8 @@ export const BlankPage = function BlankPage({
}
const appCreationDisabled = !canCreateApp() || (!appsLimit?.canAddUnlimited && appsLimit?.percentage >= 100);
const workflowsCreationDisabled =
!canCreateApp() || (!workflowsLimit?.canAddUnlimited && workflowsLimit?.percentage >= 100);
const templateOptionsView = (
<>
@ -133,12 +136,12 @@ export const BlankPage = function BlankPage({
<div className="row mt-4">
<div className="col-6">
<ButtonSolid
disabled={appCreationDisabled}
leftIcon="plus"
onClick={openCreateAppModal}
isLoading={creatingApp}
data-cy="button-new-app-from-scratch"
className="col"
disabled={appType !== 'workflow' ? appCreationDisabled : workflowsCreationDisabled}
fill={'#FDFDFE'}
>
Create new {appType !== 'workflow' ? 'application' : 'workflow'}

View file

@ -511,7 +511,12 @@ class HomePageComponent extends React.Component {
this.state.currentFolder.id
);
this.fetchFolders();
this.fetchAppsLimit();
if (this.props.appType === 'workflow') {
this.fetchWorkflowsInstanceLimit();
this.fetchWorkflowsWorkspaceLimit();
} else {
this.fetchAppsLimit();
}
})
.catch(({ error }) => {
toast.error('Could not delete the app.');
@ -522,6 +527,10 @@ class HomePageComponent extends React.Component {
});
};
isExistingPlanUser = (date) => {
return new Date(date) < new Date('2025-04-01');
};
pageCount = () => {
return this.state.currentFolder.id ? this.state.meta.folder_count : this.state.meta.total_count;
};
@ -928,6 +937,8 @@ class HomePageComponent extends React.Component {
dependentPlugins: dependentPlugins,
},
};
const isAdmin = authenticationService?.currentSessionValue?.admin;
const isBuilder = authenticationService?.currentSessionValue?.is_builder;
return (
<Layout switchDarkMode={this.props.switchDarkMode} darkMode={this.props.darkMode}>
<div className="wrapper home-page">
@ -1231,31 +1242,6 @@ class HomePageComponent extends React.Component {
</Dropdown>
</div>
</LicenseTooltip>
{this.props.appType === 'front-end' && (
<LicenseBanner classes="mb-3 small" limits={appsLimit} type="apps" size="small" />
)}
{this.props.appType === 'workflow' &&
(workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 ||
workflowWorkspaceLevelLimit.current >= workflowWorkspaceLevelLimit.total ||
100 > workflowWorkspaceLevelLimit.percentage >= 90 ||
workflowWorkspaceLevelLimit.current === workflowWorkspaceLevelLimit.total - 1) && (
<>
<LicenseBanner
classes="mb-3 small"
limits={
workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
? workflowInstanceLevelLimit
: workflowWorkspaceLevelLimit
}
type="workflow"
size="small"
/>
</>
)}
</div>
)}
<Folders
@ -1271,6 +1257,31 @@ class HomePageComponent extends React.Component {
canCreateApp={this.canCreateApp()}
appType={this.props.appType}
/>
{this.props.appType === 'front-end' && (
<LicenseBanner classes="mb-3 small" limits={appsLimit} type="apps" size="small" />
)}
{this.props.appType === 'workflow' &&
(workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 ||
workflowWorkspaceLevelLimit.current >= workflowWorkspaceLevelLimit.total ||
100 > workflowWorkspaceLevelLimit.percentage >= 90 ||
workflowWorkspaceLevelLimit.current === workflowWorkspaceLevelLimit.total - 1) && (
<>
<LicenseBanner
classes="mb-3 small"
limits={
workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
? workflowInstanceLevelLimit
: workflowWorkspaceLevelLimit
}
type="workflow"
size="small"
/>
</>
)}
{authenticationService.currentSessionValue?.super_admin &&
this.isWithinSevenDaysOfSignUp(authenticationService.currentSessionValue?.consultation_banner_date) && (
<ConsultationBanner
@ -1284,7 +1295,7 @@ class HomePageComponent extends React.Component {
/>
)}
<OrganizationList />
<OrganizationList customStyle={{ marginBottom: isAdmin || isBuilder ? '' : '0px' }} />
</div>
<div
@ -1359,6 +1370,13 @@ class HomePageComponent extends React.Component {
viewTemplateLibraryModal={this.showTemplateLibraryModal}
hideTemplateLibraryModal={this.hideTemplateLibraryModal}
appType={this.props.appType}
workflowsLimit={
workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total ||
100 > workflowInstanceLevelLimit.percentage >= 90 ||
workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
? workflowInstanceLevelLimit
: workflowWorkspaceLevelLimit
}
/>
)}
{!isLoading && apps?.length === 0 && appSearchKey && (

View file

@ -88,10 +88,11 @@ function SettingsPage(props) {
const handlePasswordInput = (input) => {
setNewPassword(input);
if (input.length > 100) {
const trimmedInput = input.trim();
if (trimmedInput.length > 100) {
setHelperText('Password should be Max 100 characters');
setValidPassword(false);
} else if (input.length < 5 && input.length > 0) {
} else if (trimmedInput.length < 5 && trimmedInput.length > 0) {
setHelperText('Password should be at least 5 characters');
setValidPassword(false);
} else {
@ -100,11 +101,19 @@ function SettingsPage(props) {
}
};
const handleConfirmPasswordInput = (input) => {
setConfirmPassword(input);
};
const changePassword = async () => {
const trimmedCurrentPassword = currentpassword.trim();
const trimmedNewPassword = newPassword.trim();
const trimmedConfirmPassword = confirmPassword.trim();
const errorMsg =
(currentpassword.match(/^ *$/) !== null && 'Current password') ||
(newPassword.match(/^ *$/) !== null && 'New password') ||
(confirmPassword.match(/^ *$/) !== null && 'Confirm new password');
(trimmedCurrentPassword.length === 0 && 'Current password') ||
(trimmedNewPassword.length === 0 && 'New password') ||
(trimmedConfirmPassword.length === 0 && 'Confirm new password');
if (errorMsg) {
toast.error(errorMsg + " can't be empty!", {
@ -112,13 +121,13 @@ function SettingsPage(props) {
});
return;
}
if (currentpassword === newPassword) {
if (trimmedCurrentPassword === trimmedNewPassword) {
toast.error("New password can't be the same as the current one!", {
duration: 3000,
});
return;
}
if (newPassword !== confirmPassword) {
if (trimmedNewPassword !== trimmedConfirmPassword) {
toast.error('New password and confirm new password should be same', {
duration: 3000,
});
@ -127,7 +136,7 @@ function SettingsPage(props) {
setPasswordChangeInProgress(true);
try {
await userService.changePassword(currentpassword, newPassword);
await userService.changePassword(trimmedCurrentPassword, trimmedNewPassword);
toast.success('Password updated successfully', {
duration: 3000,
});
@ -293,7 +302,7 @@ function SettingsPage(props) {
placeholder={t('header.profileSettingPage.confirmNewPassword', 'Confirm new password')}
value={confirmPassword}
ref={focusRef}
onChange={(event) => setConfirmPassword(event.target.value)}
onChange={(event) => handleConfirmPasswordInput(event.target.value)}
onKeyPress={confirmPasswordKeyPressHandler}
data-cy="confirm-password-input"
/>
@ -301,7 +310,7 @@ function SettingsPage(props) {
</div>
<ButtonSolid
isLoading={passwordChangeInProgress}
disabled={newPassword.length < 5 || confirmPassword.length < 5 || !validPassword}
disabled={newPassword.trim().length < 5 || confirmPassword.trim().length < 5 || !validPassword}
onClick={changePassword}
data-cy="change-password-button"
>

View file

@ -67,7 +67,7 @@ const LegalReasonsErrorModal = ({
<Button className="upgrade-btn" autoFocus onClick={() => {}}>
<a
style={{ color: 'white', textDecoration: 'none' }}
href={`https://www.tooljet.com/pricing?utm_source=banner&utm_medium=plg&utm_campaign=none&payment=onpremise&instance_id=${currentUser?.instance_id}`}
href={`https://www.tooljet.com/pricing`}
target="_blank"
rel="noopener noreferrer"
data-cy="upgrade-button"

View file

@ -192,8 +192,21 @@ export const returnWorkspaceIdIfNeed = (path) => {
};
export const getRedirectURL = (path) => {
let redirectLoc = '/';
const instanceLevelRoutes = [
'/all-users',
'/all-workspaces',
'/manage-instance-settings',
'/white-labelling',
'/instance-login',
'/smtp',
'/license',
];
if (path) {
redirectLoc = `${returnWorkspaceIdIfNeed(path)}${path !== '/' ? path : ''}`;
if (instanceLevelRoutes.includes(path)) {
redirectLoc = `/settings${path}`;
} else {
redirectLoc = `${returnWorkspaceIdIfNeed(path)}${path !== '/' ? path : ''}`;
}
} else {
const redirectTo = getRedirectTo();
const { from } = redirectTo ? { from: { pathname: redirectTo } } : { from: { pathname: '/' } };

View file

@ -2,16 +2,21 @@ import HttpClient from '@/_helpers/http-client';
const adapter = new HttpClient();
//Uncomment when Comment Notifications Module is ready
function findAll(isRead = false) {
return adapter.get(`/comment_notifications?isRead=${isRead}`);
return { data: [] };
// return adapter.get(`/comment_notifications?isRead=${isRead}`);
}
function updateAll(isRead) {
return adapter.patch(`/comment_notifications`, { isRead });
return;
// return adapter.patch(`/comment_notifications`, { isRead });
}
function update(id, isRead) {
return adapter.patch(`/comment_notifications/${id}`, { isRead });
return;
// return adapter.patch(`/comment_notifications/${id}`, { isRead });
}
export const commentNotificationsService = {

View file

@ -17,7 +17,7 @@ export const organizationService = {
function getUsersByValue(searchInput) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
return fetch(`${config.apiUrl}/organizations/users/suggest?input=${searchInput}`, requestOptions).then(
return fetch(`${config.apiUrl}/organization-users/users/suggest?input=${searchInput}`, requestOptions).then(
handleResponse
);
}

View file

@ -49,6 +49,7 @@
align-items: center;
justify-content: center;
border-top: 1px solid var(--slate5);
margin-bottom: var(--dynamic-margin, 0px); //please Remove after Basicplan banner is removed..
}
.tj-org-select {

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