Merge branch 'main' of https://github.com/ToolJet/ToolJet into appbuilder-1.6

This commit is contained in:
Kavin Venkatachalam 2024-02-27 17:33:19 +05:30
commit 011c77ba37
514 changed files with 27296 additions and 9236 deletions

View file

@ -37,12 +37,30 @@ jobs:
- name: use mybuilder buildx
run: docker buildx use mybuilder
- name: Build docker image
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypressplaform
- name: Docker Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set SAFE_BRANCH_NAME
run: echo "SAFE_BRANCH_NAME=$(echo ${{ env.BRANCH_NAME }} | tr '/' '-')" >> $GITHUB_ENV
- name: Build and Push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tj-osv:${{ env.SAFE_BRANCH_NAME }}
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up environment variables
run: |
echo "TOOLJET_HOST=http://localhost:80" >> .env
echo "TOOLJET_HOST=http://localhost:3000" >> .env
echo "LOCKBOX_MASTER_KEY=cd97331a419c09387bef49787f7da8d2a81d30733f0de6bed23ad8356d2068b2" >> .env
echo "SECRET_KEY_BASE=7073b9a35a15dd20914ae17e36a693093f25b74b96517a5fec461fc901c51e011cd142c731bee48c5081ec8bac321c1f259ef097ef2a16f25df17a3798c03426" >> .env
echo "PG_DB=tooljet_development" >> .env
@ -62,10 +80,18 @@ jobs:
echo "SSO_GIT_OAUTH2_CLIENT_SECRET=dummy" >> .env
echo "SSO_GIT_OAUTH2_HOST=dummy" >> .env
echo "SSO_GOOGLE_OAUTH2_CLIENT_ID=dummy" >> .env
echo "ENABLE_MARKETPLACE_FEATURE=true" >> .env
- name: Pulling the docker-compose file
run: curl -LO https://tooljet-test.s3.us-west-1.amazonaws.com/docker-compose.yaml && mkdir postgres_data
- name: Update docker-compose file
run: |
# 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
- name: Run docker-compose file
run: docker-compose up -d
@ -78,7 +104,7 @@ jobs:
- name: Wait for the server to be ready
run: |
timeout 1500 bash -c '
until curl --silent --fail http://localhost:80; do
until curl --silent --fail http://localhost:3000; do
sleep 5
done'
@ -97,7 +123,7 @@ jobs:
uses: cypress-io/github-action@v5
with:
working-directory: ./cypress-tests
config: "baseUrl=http://localhost:80"
config: "baseUrl=http://localhost:3000"
config-file: cypress-marketplace.config.js
- name: Capture Screenshots
@ -107,6 +133,7 @@ jobs:
name: screenshots
path: cypress-tests/cypress/screenshots
Cypress-Marketplace-Subpath:
runs-on: ubuntu-22.04

View file

@ -1 +1 @@
2.29.0
2.30.3

View file

@ -256,8 +256,7 @@ export const commonSelectors = {
.toLowerCase()}-label"]`;
},
defaultModalTitle: '[data-cy="modal-title"]',
workspaceConstantsIcon: '[data-cy="icon-workspace-constants"]'
workspaceConstantsIcon: '[data-cy="icon-workspace-constants"]',
};
export const commonWidgetSelector = {
@ -268,7 +267,9 @@ export const commonWidgetSelector = {
draggableWidget: (widgetName) => {
return `[data-cy=draggable-widget-${cyParamName(widgetName)}]`;
},
textInputInputField: (widgetName) => {
return `[data-cy=input-${cyParamName(widgetName)}]`;
},
parameterLabel: (paramName) => {
return `[data-cy="${cyParamName(paramName)}-widget-parameter-label"]`;
},

View file

@ -3,14 +3,14 @@ export const postgreSqlText = {
labelAddDataSource: "+ add data source",
allDataSources: () => {
return process.env.NODE_ENV === "development"
? "All data sources (41)"
: "All data sources (43)";
return Cypress.env("marketplace_action")
? "All data sources (43)"
: "All data sources (41)";
},
allDatabase: () => {
return process.env.NODE_ENV === "development"
? "Databases (17)"
: "Databases (19)";
return Cypress.env("marketplace_action")
? "Databases (19)"
: "Databases (17)";
},
allApis: "APIs (20)",
allCloudStorage: "Cloud Storages (4)",

View file

@ -13,13 +13,15 @@ import { dataSourceSelector } from "Selectors/dataSource";
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.customText = fake.randomSentence;
describe("Data source Azure Blob Storage", () => {
beforeEach(() => {
cy.appUILogin();
cy.intercept("GET", "/api/v2/data_sources");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on Azure Blob Storage connection form", () => {
@ -48,7 +50,7 @@ describe("Data source Azure Blob Storage", () => {
selectAndAddDataSource(
"cloudstorage",
azureBlobStorageText.azureBlobStorage,
data.lastName
data.dataSourceName
);
// cy.get("[data-cy*='data-source-']")
@ -97,7 +99,7 @@ describe("Data source Azure Blob Storage", () => {
"have.text",
"Cannot read properties of undefined (reading 'startsWith')"
);
deleteDatasource(`cypress-${data.lastName}-azure-blob-storage`);
deleteDatasource(`cypress-${data.dataSourceName}-azure-blob-storage`);
});
it("Should verify the functionality of Azure Blob Storage connection form.", () => {
@ -110,7 +112,7 @@ describe("Data source Azure Blob Storage", () => {
selectAndAddDataSource(
"cloudstorage",
azureBlobStorageText.azureBlobStorage,
data.lastName
data.dataSourceName
);
fillDataSourceTextField(
@ -155,12 +157,12 @@ describe("Data source Azure Blob Storage", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-azure-blob-storage-button"]`
`[data-cy="cypress-${data.dataSourceName}-azure-blob-storage-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.lastName}-azure-blob-storage`
`cypress-${data.dataSourceName}-azure-blob-storage`
);
deleteDatasource(`cypress-${data.lastName}-azure-blob-storage`);
deleteDatasource(`cypress-${data.dataSourceName}-azure-blob-storage`);
});
});

View file

@ -12,12 +12,14 @@ import { commonText } from "Texts/common";
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source BigQuery", () => {
beforeEach(() => {
cy.appUILogin();
cy.intercept("GET", "/api/v2/data_sources");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on BigQuery connection form", () => {
@ -44,7 +46,11 @@ describe("Data source BigQuery", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", bigqueryText.bigQuery, data.lastName);
selectAndAddDataSource(
"databases",
bigqueryText.bigQuery,
data.dataSourceName
);
cy.get('[data-cy="label-private-key"]').verifyVisibleElement(
"have.text",
@ -83,14 +89,18 @@ describe("Data source BigQuery", () => {
bigqueryText.errorInvalidEmailId
);
deleteDatasource(
`cypress-${String(data.lastName).toLowerCase()}-${String(
`cypress-${String(data.dataSourceName).toLowerCase()}-${String(
bigqueryText.bigQuery
).toLowerCase()}`
);
});
it("Should verify the functionality of BigQuery connection form.", () => {
selectAndAddDataSource("databases", bigqueryText.bigQuery, data.lastName);
selectAndAddDataSource(
"databases",
bigqueryText.bigQuery,
data.dataSourceName
);
fillDataSourceTextField(
firestoreText.privateKey,
@ -112,9 +122,12 @@ describe("Data source BigQuery", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-bigquery-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-bigquery`);
`[data-cy="cypress-${data.dataSourceName}-bigquery-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-bigquery`
);
deleteDatasource(`cypress-${data.lastName}-bigquery`);
deleteDatasource(`cypress-${data.dataSourceName}-bigquery`);
});
});

View file

@ -17,11 +17,13 @@ import {
} from "Support/utils/postgreSql";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -45,7 +47,7 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "ClickHouse", data.lastName);
selectAndAddDataSource("databases", "ClickHouse", data.dataSourceName);
// cy.get(postgreSqlSelector.dataSourceNameInputField).should(
// //username,password,host,port,protocol,dbname,usepost, trimquery,gzip,debug,raw
@ -122,11 +124,11 @@ describe("Data sources", () => {
cy.get('[data-cy="connection-alert-text"]', { timeout: 60000 })
.scrollIntoView()
.verifyVisibleElement("have.text", "getaddrinfo ENOTFOUND undefined");
deleteDatasource(`cypress-${data.lastName}-clickhouse`);
deleteDatasource(`cypress-${data.dataSourceName}-clickhouse`);
});
it("Should verify the functionality of PostgreSQL connection form.", () => {
selectAndAddDataSource("databases", "ClickHouse", data.lastName);
selectAndAddDataSource("databases", "ClickHouse", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -162,8 +164,11 @@ describe("Data sources", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-clickhouse-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-clickhouse`);
deleteDatasource(`cypress-${data.lastName}-clickhouse`);
`[data-cy="cypress-${data.dataSourceName}-clickhouse-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-clickhouse`
);
deleteDatasource(`cypress-${data.dataSourceName}-clickhouse`);
});
});

View file

@ -17,11 +17,13 @@ import {
} from "Support/utils/postgreSql";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -44,7 +46,7 @@ describe("Data sources", () => {
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "CosmosDB", data.lastName);
selectAndAddDataSource("databases", "CosmosDB", data.dataSourceName);
cy.get('[data-cy="label-end-point"]').verifyVisibleElement(
"have.text",
@ -83,11 +85,11 @@ describe("Data sources", () => {
"have.text",
"Invalid URL"
);
deleteDatasource(`cypress-${data.lastName}-cosmosdb`);
deleteDatasource(`cypress-${data.dataSourceName}-cosmosdb`);
});
it.only("Should verify the functionality of CosmosDB connection form.", () => {
selectAndAddDataSource("databases", "CosmosDB", data.lastName);
selectAndAddDataSource("databases", "CosmosDB", data.dataSourceName);
fillDataSourceTextField(
"End point",
@ -112,8 +114,11 @@ describe("Data sources", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-cosmosdb-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-cosmosdb`);
deleteDatasource(`cypress-${data.lastName}-cosmosdb`);
`[data-cy="cypress-${data.dataSourceName}-cosmosdb-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-cosmosdb`
);
deleteDatasource(`cypress-${data.dataSourceName}-cosmosdb`);
});
});

View file

@ -18,11 +18,13 @@ import {
} from "Support/utils/postgreSql";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on CouchDB connection form", () => {
@ -46,7 +48,7 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "CouchDB", data.lastName);
selectAndAddDataSource("databases", "CouchDB", data.dataSourceName);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -104,11 +106,11 @@ describe("Data sources", () => {
"have.text",
"Invalid URL"
);
deleteDatasource(`cypress-${data.lastName}-couchdb`);
deleteDatasource(`cypress-${data.dataSourceName}-couchdb`);
});
it("Should verify the functionality of CouchDB connection form.", () => {
selectAndAddDataSource("databases", "CouchDB", data.lastName);
selectAndAddDataSource("databases", "CouchDB", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -145,8 +147,11 @@ describe("Data sources", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-couchdb-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-couchdb`);
deleteDatasource(`cypress-${data.lastName}-couchdb`);
`[data-cy="cypress-${data.dataSourceName}-couchdb-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-couchdb`
);
deleteDatasource(`cypress-${data.dataSourceName}-couchdb`);
});
});

View file

@ -16,11 +16,13 @@ import {
} from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source DynamoDB", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on DynamoDB connection form", () => {
@ -44,7 +46,11 @@ describe("Data source DynamoDB", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", dynamoDbText.dynamoDb, data.lastName);
selectAndAddDataSource(
"databases",
dynamoDbText.dynamoDb,
data.dataSourceName
);
cy.get('[data-cy="label-region"]').verifyVisibleElement(
"have.text",
@ -90,11 +96,15 @@ describe("Data source DynamoDB", () => {
"have.text",
dynamoDbText.errorMissingRegion
);
deleteDatasource(`cypress-${data.lastName}-dynamodb`);
deleteDatasource(`cypress-${data.dataSourceName}-dynamodb`);
});
it("Should verify the functionality of DynamoDB connection form.", () => {
selectAndAddDataSource("databases", dynamoDbText.dynamoDb, data.lastName);
selectAndAddDataSource(
"databases",
dynamoDbText.dynamoDb,
data.dataSourceName
);
cy.get('[data-cy="label-region"]')
.parent()
@ -147,9 +157,12 @@ describe("Data source DynamoDB", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-dynamodb-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-dynamodb`);
`[data-cy="cypress-${data.dataSourceName}-dynamodb-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-dynamodb`
);
deleteDatasource(`cypress-${data.lastName}-dynamodb`);
deleteDatasource(`cypress-${data.dataSourceName}-dynamodb`);
});
});

View file

@ -15,11 +15,10 @@ import {
} from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Elasticsearch", () => {
beforeEach(() => {
cy.appUILogin();
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on Elasticsearch connection form", () => {

View file

@ -14,11 +14,13 @@ import {
selectAndAddDataSource,
} from "Support/utils/postgreSql";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Firestore", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on Firestore connection form", () => {
@ -41,7 +43,11 @@ describe("Data source Firestore", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", firestoreText.firestore, data.lastName);
selectAndAddDataSource(
"databases",
firestoreText.firestore,
data.dataSourceName
);
cy.get('[data-cy="label-private-key"]').verifyVisibleElement(
"have.text",
@ -79,11 +85,15 @@ describe("Data source Firestore", () => {
"have.text",
firestoreText.errorGcpKeyCouldNotBeParsed
);
deleteDatasource(`cypress-${data.lastName}-firestore`);
deleteDatasource(`cypress-${data.dataSourceName}-firestore`);
});
it("Should verify the functionality of Firestore connection form.", () => {
selectAndAddDataSource("databases", firestoreText.firestore, data.lastName);
selectAndAddDataSource(
"databases",
firestoreText.firestore,
data.dataSourceName
);
fillDataSourceTextField(
firestoreText.privateKey,
@ -104,9 +114,12 @@ describe("Data source Firestore", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-firestore-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-firestore`);
`[data-cy="cypress-${data.dataSourceName}-firestore-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-firestore`
);
deleteDatasource(`cypress-${data.lastName}-firestore`);
deleteDatasource(`cypress-${data.dataSourceName}-firestore`);
});
});

View file

@ -20,11 +20,13 @@ import {
} from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -48,7 +50,7 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "InfluxDB", data.lastName);
selectAndAddDataSource("databases", "InfluxDB", data.dataSourceName);
cy.get('[data-cy="label-api-token"]').verifyVisibleElement(
"have.text",
@ -97,11 +99,11 @@ describe("Data sources", () => {
"have.text",
"Invalid URL"
);
deleteDatasource(`cypress-${data.lastName}-influxdb`);
deleteDatasource(`cypress-${data.dataSourceName}-influxdb`);
});
it("Should verify the functionality of PostgreSQL connection form.", () => {
selectAndAddDataSource("databases", "InfluxDB", data.lastName);
selectAndAddDataSource("databases", "InfluxDB", data.dataSourceName);
fillDataSourceTextField(
"API token",
@ -129,9 +131,12 @@ describe("Data sources", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-influxdb-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-influxdb`);
`[data-cy="cypress-${data.dataSourceName}-influxdb-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-influxdb`
);
deleteDatasource(`cypress-${data.lastName}-influxdb`);
deleteDatasource(`cypress-${data.dataSourceName}-influxdb`);
});
});

View file

@ -16,11 +16,13 @@ import { fake } from "Fixtures/fake";
import { closeDSModal, deleteDatasource } from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -44,7 +46,7 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "MariaDB", data.lastName);
selectAndAddDataSource("databases", "MariaDB", data.dataSourceName);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -106,11 +108,11 @@ describe("Data sources", () => {
postgreSqlText.buttonTextSave
);
// cy.get('[data-cy="connection-alert-text"]').should("be.visible")
deleteDatasource(`cypress-${data.lastName}-mariadb`);
deleteDatasource(`cypress-${data.dataSourceName}-mariadb`);
});
it("Should verify the functionality of MariaDB connection form.", () => {
selectAndAddDataSource("databases", "MariaDB", data.lastName);
selectAndAddDataSource("databases", "MariaDB", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -161,9 +163,12 @@ describe("Data sources", () => {
);
cy.get(
`[data-cy="cypress-${data.lastName}-mariadb-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-mariadb`);
`[data-cy="cypress-${data.dataSourceName}-mariadb-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-mariadb`
);
deleteDatasource(`cypress-${data.lastName}-mariadb`);
deleteDatasource(`cypress-${data.dataSourceName}-mariadb`);
});
});

View file

@ -24,11 +24,13 @@ import {
} from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source MongoDB", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on MongoDB connection form", () => {
@ -50,7 +52,11 @@ describe("Data source MongoDB", () => {
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", mongoDbText.mongoDb, data.lastName);
selectAndAddDataSource(
"databases",
mongoDbText.mongoDb,
data.dataSourceName
);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -144,11 +150,15 @@ describe("Data source MongoDB", () => {
.verifyVisibleElement("have.text", postgreSqlText.buttonTextSave)
.click();
deleteDatasource(`cypress-${data.lastName}-mongodb`);
deleteDatasource(`cypress-${data.dataSourceName}-mongodb`);
});
it("Should verify the functionality of MongoDB connection form.", () => {
selectAndAddDataSource("databases", mongoDbText.mongoDb, data.lastName);
selectAndAddDataSource(
"databases",
mongoDbText.mongoDb,
data.dataSourceName
);
cy.get('[data-cy="query-select-dropdown"]').type(
mongoDbText.optionConnectUsingConnectionString
@ -174,10 +184,13 @@ describe("Data source MongoDB", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-mongodb-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-mongodb`);
`[data-cy="cypress-${data.dataSourceName}-mongodb-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-mongodb`
);
deleteDatasource(`cypress-${data.lastName}-mongodb`);
deleteDatasource(`cypress-${data.dataSourceName}-mongodb`);
});
it.skip("Should verify the queries of MongoDB.", () => {

View file

@ -22,11 +22,13 @@ import {
import { realHover } from "cypress-real-events/commands/realHover";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources MySql", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on MySQL connection form", () => {
@ -50,7 +52,7 @@ describe("Data sources MySql", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "MySQL", data.lastName);
selectAndAddDataSource("databases", "MySQL", data.dataSourceName);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -101,11 +103,11 @@ describe("Data sources MySql", () => {
postgreSqlText.buttonTextSave
);
verifyCouldnotConnectWithAlert(mySqlText.errorConnectionRefused);
deleteDatasource(`cypress-${data.lastName}-mysql`);
deleteDatasource(`cypress-${data.dataSourceName}-mysql`);
});
it("Should verify the functionality of MySQL connection form.", () => {
selectAndAddDataSource("databases", "MySQL", data.lastName);
it.only("Should verify the functionality of MySQL connection form.", () => {
selectAndAddDataSource("databases", "MySQL", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -115,7 +117,7 @@ describe("Data sources MySql", () => {
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"3318"
"3306"
);
fillDataSourceTextField(
postgreSqlText.labelDbName,
@ -134,6 +136,7 @@ describe("Data sources MySql", () => {
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.wait(500);
verifyCouldnotConnectWithAlert("");
fillDataSourceTextField(
postgreSqlText.labelDbName,
@ -146,6 +149,7 @@ describe("Data sources MySql", () => {
"admin1"
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.wait(500);
verifyCouldnotConnectWithAlert(
"ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client"
);
@ -158,6 +162,7 @@ describe("Data sources MySql", () => {
cy.get(postgreSqlSelector.passwordTextField).type("testpassword");
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.wait(500);
verifyCouldnotConnectWithAlert(
"ER_ACCESS_DENIED_ERROR: Access denied for user 'root'@'103.171.99.42' (using password: YES)"
);
@ -193,15 +198,15 @@ describe("Data sources MySql", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-mysql-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-mysql`);
`[data-cy="cypress-${data.dataSourceName}-mysql-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dataSourceName}-mysql`);
deleteDatasource(`cypress-${data.lastName}-mysql`);
deleteDatasource(`cypress-${data.dataSourceName}-mysql`);
});
it.skip("Should verify elements of the Query section.", () => {
cy.viewport(1200, 1300);
selectAndAddDataSource("databases", "MySQL", data.lastName);
selectAndAddDataSource("databases", "MySQL", data.dataSourceName);
fillConnectionForm({
Host: Cypress.env("mysql_host"),
Port: Cypress.env("mysql_port"),
@ -371,7 +376,7 @@ describe("Data sources MySql", () => {
it.skip("Should verify CRUD operations on SQL Query.", () => {
let dbName = "7mmplik";
selectAndAddDataSource("databases", "MySQL", data.lastName);
selectAndAddDataSource("databases", "MySQL", data.dataSourceName);
cy.clearAndType(
postgreSqlSelector.dataSourceNameInputField,
@ -450,14 +455,14 @@ describe("Data sources MySql", () => {
});
it.skip("Should verify bulk update", () => {
selectAndAddDataSource("databases", "MySQL", data.lastName);
selectAndAddDataSource("databases", "MySQL", data.dataSourceName);
cy.clearAndType(
postgreSqlSelector.dataSourceNameInputField,
mySqlText.cypressMySql
);
fillConnectionForm({
Host: Cypress.env("mysql_host"),
Port: "3318",
Port: "3306",
"Database Name": "testdv",
Username: Cypress.env("mysql_user"),
Password: Cypress.env("mysql_password"),

View file

@ -16,11 +16,13 @@ import {
import { deleteDatasource } from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -49,7 +51,7 @@ describe("Data sources", () => {
selectAndAddDataSource(
"databases",
postgreSqlText.postgreSQL,
data.lastName
data.dataSourceName
);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
@ -108,14 +110,14 @@ describe("Data sources", () => {
postgreSqlText.buttonTextSave
);
cy.get('[data-cy="connection-alert-text"]').should("be.visible");
deleteDatasource(`cypress-${data.lastName}-postgresql`);
deleteDatasource(`cypress-${data.dataSourceName}-postgresql`);
});
it("Should verify the functionality of PostgreSQL connection form.", () => {
selectAndAddDataSource(
"databases",
postgreSqlText.postgreSQL,
data.lastName
data.dataSourceName
);
fillDataSourceTextField(
@ -162,17 +164,20 @@ describe("Data sources", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-postgresql-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-postgresql`);
`[data-cy="cypress-${data.dataSourceName}-postgresql-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-postgresql`
);
deleteDatasource(`cypress-${data.lastName}-postgresql`);
deleteDatasource(`cypress-${data.dataSourceName}-postgresql`);
});
it.skip("Should verify elements of the Query section.", () => {
selectAndAddDataSource(
"databases",
postgreSqlText.postgreSQL,
data.lastName
data.dataSourceName
);
fillConnectionForm(
{
@ -367,7 +372,7 @@ describe("Data sources", () => {
selectAndAddDataSource(
"databases",
postgreSqlText.postgreSQL,
data.lastName
data.dataSourceName
);
cy.clearAndType(
@ -458,7 +463,7 @@ describe("Data sources", () => {
selectAndAddDataSource(
"databases",
postgreSqlText.postgreSQL,
data.lastName
data.dataSourceName
);
fillConnectionForm({
Host: Cypress.env("pg_host"),

View file

@ -15,11 +15,13 @@ import {
} from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Redis", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connecti Redison form", () => {
@ -43,7 +45,7 @@ describe("Data source Redis", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", redisText.redis, data.lastName);
selectAndAddDataSource("databases", redisText.redis, data.dataSourceName);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
postgreSqlText.labelHost
@ -99,10 +101,10 @@ describe("Data source Redis", () => {
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.lastName}-redis`);
deleteDatasource(`cypress-${data.dataSourceName}-redis`);
});
it("Should verify the functionality of Redis connection form.", () => {
selectAndAddDataSource("databases", redisText.redis, data.lastName);
selectAndAddDataSource("databases", redisText.redis, data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -201,9 +203,9 @@ describe("Data source Redis", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-redis-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-redis`);
`[data-cy="cypress-${data.dataSourceName}-redis-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dataSourceName}-redis`);
deleteDatasource(`cypress-${data.lastName}-redis`);
deleteDatasource(`cypress-${data.dataSourceName}-redis`);
});
});

View file

@ -15,11 +15,13 @@ import {
} from "Support/utils/postgreSql";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -43,7 +45,7 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "RethinkDB", data.lastName);
selectAndAddDataSource("databases", "RethinkDB", data.dataSourceName);
cy.get('[data-cy="label-database"]').verifyVisibleElement(
"have.text",
@ -101,7 +103,7 @@ describe("Data sources", () => {
});
it("Should verify the functionality of RethinkDB connection form.", () => {
selectAndAddDataSource("databases", "RethinkDB", data.lastName);
selectAndAddDataSource("databases", "RethinkDB", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,

View file

@ -16,11 +16,13 @@ import {
} from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources AWS S3", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on AWS S3 connection form", () => {
@ -44,7 +46,7 @@ describe("Data sources AWS S3", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("cloudstorage", s3Text.awsS3, data.lastName);
selectAndAddDataSource("cloudstorage", s3Text.awsS3, data.dataSourceName);
cy.get(s3Selector.accessKeyLabel).verifyVisibleElement(
"have.text",
s3Text.accessKey
@ -100,11 +102,11 @@ describe("Data sources AWS S3", () => {
commonSelectors.toastMessage,
postgreSqlText.toastDSSaved
);
deleteDatasource(`cypress-${data.lastName}-aws-s3`);
deleteDatasource(`cypress-${data.dataSourceName}-aws-s3`);
});
it("Should verify the functionality of AWS S3 connection form.", () => {
selectAndAddDataSource("cloudstorage", s3Text.awsS3, data.lastName);
selectAndAddDataSource("cloudstorage", s3Text.awsS3, data.dataSourceName);
fillDataSourceTextField(
s3Text.accessKey,
@ -189,8 +191,11 @@ describe("Data sources AWS S3", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-aws-s3-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-aws-s3`);
deleteDatasource(`cypress-${data.lastName}-aws-s3`);
`[data-cy="cypress-${data.dataSourceName}-aws-s3-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-aws-s3`
);
deleteDatasource(`cypress-${data.dataSourceName}-aws-s3`);
});
});

View file

@ -10,11 +10,13 @@ import {
import { deleteDatasource, closeDSModal } from "Support/utils/dataSource";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source SMTP", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on SMTP connection form", () => {
@ -37,7 +39,7 @@ describe("Data source SMTP", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("apis", "SMTP", data.lastName);
selectAndAddDataSource("apis", "SMTP", data.dataSourceName);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -84,11 +86,11 @@ describe("Data source SMTP", () => {
"have.text",
"Invalid credentials"
);
deleteDatasource(`cypress-${data.lastName}-smtp`);
deleteDatasource(`cypress-${data.dataSourceName}-smtp`);
});
it("Should verify the functionality of SMTP connection form.", () => {
selectAndAddDataSource("apis", "SMTP", data.lastName);
selectAndAddDataSource("apis", "SMTP", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -128,8 +130,8 @@ describe("Data source SMTP", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-smtp-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-smtp`);
deleteDatasource(`cypress-${data.lastName}-smtp`);
`[data-cy="cypress-${data.dataSourceName}-smtp-button"]`
).verifyVisibleElement("have.text", `cypress-${data.dataSourceName}-smtp`);
deleteDatasource(`cypress-${data.dataSourceName}-smtp`);
});
});

View file

@ -16,14 +16,15 @@ import {
addWidgetsToAddUser,
} from "Support/utils/postgreSql";
const data = {};
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
it("Should verify elements on connection form", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
closeDSModal();
@ -44,7 +45,7 @@ describe("Data sources", () => {
"have.text",
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "Snowflake", data.lastName);
selectAndAddDataSource("databases", "Snowflake", data.dataSourceName);
cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement(
"have.text",
@ -105,11 +106,11 @@ describe("Data sources", () => {
"have.text",
"Invalid account. The specified value must be a valid subdomain string."
);
deleteDatasource(`cypress-${data.lastName}-snowflake`);
deleteDatasource(`cypress-${data.dataSourceName}-snowflake`);
});
it.skip("Should verify the functionality of PostgreSQL connection form.", () => {
selectAndAddDataSource("databases", "Snowflake", data.lastName);
selectAndAddDataSource("databases", "Snowflake", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelUserName,
@ -151,9 +152,12 @@ describe("Data sources", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-snowflake-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-snowflake`);
`[data-cy="cypress-${data.dataSourceName}-snowflake-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-snowflake`
);
deleteDatasource(`cypress-${data.lastName}-snowflake`);
deleteDatasource(`cypress-${data.dataSourceName}-snowflake`);
});
});

View file

@ -17,11 +17,13 @@ import {
} from "Support/utils/postgreSql";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -45,7 +47,7 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "SQL Server", data.lastName);
selectAndAddDataSource("databases", "SQL Server", data.dataSourceName);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -107,11 +109,11 @@ describe("Data sources", () => {
"have.text",
"Failed to connect to localhost:1433 - Could not connect (sequence)"
);
deleteDatasource(`cypress-${data.lastName}-sql-server`);
deleteDatasource(`cypress-${data.dataSourceName}-sql-server`);
});
it("Should verify the functionality of SQL Server connection form.", () => {
selectAndAddDataSource("databases", "SQL Server", data.lastName);
selectAndAddDataSource("databases", "SQL Server", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -158,8 +160,11 @@ describe("Data sources", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-sql-server-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-sql-server`);
deleteDatasource(`cypress-${data.lastName}-sql-server`);
`[data-cy="cypress-${data.dataSourceName}-sql-server-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-sql-server`
);
deleteDatasource(`cypress-${data.dataSourceName}-sql-server`);
});
});

View file

@ -17,11 +17,13 @@ import {
} from "Support/utils/postgreSql";
const data = {};
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data sources", () => {
beforeEach(() => {
cy.appUILogin();
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
});
it("Should verify elements on connection form", () => {
@ -45,7 +47,7 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
selectAndAddDataSource("databases", "TypeSense", data.lastName);
selectAndAddDataSource("databases", "TypeSense", data.dataSourceName);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -94,11 +96,11 @@ describe("Data sources", () => {
"have.text",
"Ensure that apiKey is set"
);
deleteDatasource(`cypress-${data.lastName}-typesense`);
deleteDatasource(`cypress-${data.dataSourceName}-typesense`);
});
it("Should verify the functionality of TypeSense connection form.", () => {
selectAndAddDataSource("databases", "TypeSense", data.lastName);
selectAndAddDataSource("databases", "TypeSense", data.dataSourceName);
fillDataSourceTextField(
postgreSqlText.labelHost,
@ -130,9 +132,12 @@ describe("Data sources", () => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(
`[data-cy="cypress-${data.lastName}-typesense-button"]`
).verifyVisibleElement("have.text", `cypress-${data.lastName}-typesense`);
`[data-cy="cypress-${data.dataSourceName}-typesense-button"]`
).verifyVisibleElement(
"have.text",
`cypress-${data.dataSourceName}-typesense`
);
deleteDatasource(`cypress-${data.lastName}-typesense`);
deleteDatasource(`cypress-${data.dataSourceName}-typesense`);
});
});

View file

@ -89,7 +89,8 @@ describe("Editor- Global Settings", () => {
cy.get('[data-cy="modal-confirm-button"]').click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"Application is on maintenance."
"Application is on maintenance.",
false
);
cy.forceClickOnCanvas();
cy.wait(500);
@ -98,6 +99,7 @@ describe("Editor- Global Settings", () => {
cy.get('[data-cy="button-release"]').click();
cy.get('[data-cy="yes-button"]').click();
cy.get('[data-cy="editor-page-logo"]').click();
cy.get('[data-cy="back-to-app-option"]').click();
cy.get(`[data-cy="${data.appName.toLowerCase()}-card"]`)
.realHover()
.find('[data-cy="launch-button"]')

View file

@ -3,7 +3,12 @@ import {
verifyMultipleComponentValuesFromInspector,
verifyComponentValueFromInspector,
} from "Support/utils/commonWidget";
import { verifyNodeData, openNode, verifyValue, deleteComponentFromInspector } from "Support/utils/inspector";
import {
verifyNodeData,
openNode,
verifyValue,
deleteComponentFromInspector,
} from "Support/utils/inspector";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { addNewPage } from "Support/utils/multipage";
import {
@ -12,19 +17,16 @@ import {
addSupportCSAData,
} from "Support/utils/events";
import { multipageSelector } from "Selectors/multipage";
import {
navigateToCreateNewVersionModal
} from "Support/utils/version";
import { navigateToCreateNewVersionModal } from "Support/utils/version";
import { createNewVersion } from "Support/utils/exportImport";
describe("Editor- Inspector", () => {
let currentVersion = "";
let newVersion = [];
let versionFrom = "";
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.apiCreateApp(`${fake.companyName}-inspector-App`);
cy.openApp();
});
@ -94,7 +96,9 @@ describe("Editor- Inspector", () => {
cy.get('[data-cy="button-add-query-param"]').click();
cy.wait(3000);
cy.get("body").then(($body) => {
if ($body.find('[data-cy="query-param-key-input-field"]').length == 0) {
if (
$body.find('[data-cy="event-query-param-key-input-field"]').length == 0
) {
cy.get('[data-cy="button-add-query-param"]').click();
}
});
@ -189,6 +193,5 @@ describe("Editor- Inspector", () => {
navigateToCreateNewVersionModal((currentVersion = "v1"));
createNewVersion((newVersion = ["v2"]), (versionFrom = "v1"));
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
});
});

View file

@ -8,7 +8,7 @@ import { buttonText } from "Texts/button";
import {
verifyControlComponentAction,
randomString,
} from "Support/utils/textInput";
} from "Support/utils/editor/textInput";
import {
openAccordion,
verifyAndModifyParameter,

View file

@ -2,7 +2,7 @@ import { fake } from "Fixtures/fake";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { openEditorSidebar } from "Support/utils/commonWidget";
import { selectEvent } from "Support/utils/events";
import { randomString } from "Support/utils/textInput";
import { randomString } from "Support/utils/editor/textInput";
import { buttonText } from "Texts/button";
import { addSuccessNotification, chainQuery } from "Support/utils/queries";
@ -12,7 +12,7 @@ import { resizeQueryPanel } from "Support/utils/dataSource";
describe("Chaining of queries", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.apiCreateApp(`${fake.companyName}-chaining-App`);
cy.openApp();
cy.viewport(1800, 1800);
cy.dragAndDropWidget("Button");
@ -58,7 +58,7 @@ describe("Chaining of queries", () => {
cy.apiCreateGDS(
"http://localhost:3000/api/v2/data_sources",
`cypress-${dsName}-postgresql`,
`cypress-${dsName}-qc-postgresql`,
"postgresql",
[
{ key: "host", value: Cypress.env("pg_host") },
@ -76,9 +76,9 @@ describe("Chaining of queries", () => {
mode: "sql",
transformationLanguage: "javascript",
enableTransformation: false,
query: `SELECT * FROM server_side_pagination`,
query: `SELECT * FROM pg_stat_activity;`,
},
`cypress-${dsName}-postgresql`,
`cypress-${dsName}-qc-postgresql`,
"postgresql"
);
cy.reload();

View file

@ -7,7 +7,7 @@ import { buttonText } from "Texts/button";
import {
verifyControlComponentAction,
randomString,
} from "Support/utils/textInput";
} from "Support/utils/editor/textInput";
import {
openAccordion,
verifyAndModifyParameter,
@ -64,7 +64,7 @@ import { deleteDownloadsFolder } from "Support/utils/common";
describe("RunJS", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.apiCreateApp(`${fake.companyName}-runjs-App`);
cy.openApp();
cy.viewport(1800, 1800);
cy.dragAndDropWidget("Button");
@ -201,6 +201,7 @@ describe("RunJS", () => {
cy.get('[data-cy="sign-in-header"]').should("be.visible");
cy.apiLogin();
cy.openApp(
Cypress.env("workspaceId"),
Cypress.env("appId"),
'[data-cy="draggable-widget-modal1-launch-button"]'
);

View file

@ -7,7 +7,7 @@ import { buttonText } from "Texts/button";
import {
verifyControlComponentAction,
randomString,
} from "Support/utils/textInput";
} from "Support/utils/editor/textInput";
import {
openAccordion,
verifyAndModifyParameter,
@ -63,7 +63,7 @@ import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector";
describe("runpy", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.apiCreateApp(`${fake.companyName}-runpy-App`);
cy.openApp();
cy.viewport(1800, 1800);
cy.dragAndDropWidget("Button");

View file

@ -6,7 +6,7 @@ import { buttonText } from "Texts/button";
import {
verifyControlComponentAction,
randomString,
} from "Support/utils/textInput";
} from "Support/utils/editor/textInput";
import {
openAccordion,
verifyAndModifyParameter,
@ -44,17 +44,17 @@ describe("Editor title", () => {
});
it("should verify titles", () => {
cy.url().should("include", "/my-workspace");
cy.title().should("eq", "ToolJet - Dashboard");
cy.title().should("eq", "Dashboard | ToolJet");
cy.log(data.appName);
cy.openApp();
cy.url().should("include", Cypress.env("appId"));
cy.title().should("eq", `${data.appName} - Tooljet`);
cy.title().should("eq", `${data.appName} | ToolJet`);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.url().should("include", `/applications/${Cypress.env("appId")}`);
cy.title().should("eq", `${data.appName}`);
cy.title().should("eq", `Preview - ${data.appName} | ToolJet`);
cy.go("back");
cy.releaseApp();
cy.url().then((url) => cy.visit(`/applications/${url.split("/").pop()}`));

View file

@ -248,7 +248,9 @@ describe("Editor- Test Button widget", () => {
buttonText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.get(
commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
@ -350,7 +352,7 @@ describe("Editor- Test Button widget", () => {
cy.wait(500);
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
cy.get(commonWidgetSelector.draggableWidget("textinput1")).should(
cy.get('[data-cy="input-textinput1"]').should(
"have.value",
data.customMessage
);
@ -400,9 +402,13 @@ describe("Editor- Test Button widget", () => {
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 150);
selectEvent("On click", "Control Component");
cy.wait("@events");
selectCSA("button1", "Disable");
cy.get('[data-cy="Value-fx-button"]').realClick();
cy.get('[data-cy="Value-input-field"]').clearAndTypeOnCodeMirror(`{{true`);
cy.wait("@events");
cy.get('[data-cy="event-Value-fx-button"]').realClick();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
`{{true`
);
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 200);
@ -414,8 +420,10 @@ describe("Editor- Test Button widget", () => {
selectEvent("On click", "Control Component");
selectCSA("button1", "Loading");
cy.wait(500);
cy.get('[data-cy="Value-fx-button"]').realClick();
cy.get('[data-cy="Value-input-field"]').clearAndTypeOnCodeMirror(`{{true`);
cy.get('[data-cy="event-Value-fx-button"]').realClick();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
`{{true`
);
cy.get(commonWidgetSelector.draggableWidget("textinput1")).type("testBtn");
cy.wait(500);
@ -444,8 +452,8 @@ describe("Editor- Test Button widget", () => {
cy.apiDeleteApp();
});
it("Should verify deletion of button component from right side panel", () => {
cy.get(".col-2").click();
cy.get(".list-item-popover-body > :nth-child(3)").click();
cy.get('[data-cy="component-inspector-options"]').click();
cy.get('[data-cy="component-inspector-delete-button"]').click();
cy.get('[data-cy="yes-button"]').click();
cy.verifyToastMessage(
`[class=go3958317564]`,

View file

@ -25,7 +25,9 @@ describe("Editor- component duplication", () => {
data.boxShadowParam = fake.boxShadowParam;
data.tooltipText = fake.randomSentence;
});
afterEach(() => {
cy.apiDeleteApp(`${fake.companyName}-App`);
});
it("should verify duplication using copy and paste", () => {
addBasicData(data);
cy.forceClickOnCanvas();

View file

@ -190,7 +190,7 @@ describe("Editor- CSA", () => {
cy.dragAndDropWidget("Button", 500, 200);
selectEvent("On click", "Control Component");
selectCSA("icon1", "Set Visibility");
cy.get('[data-cy="Value-toggle-button"]')
cy.get('[data-cy="event-Value-toggle-button"]')
.should("be.visible")
.and("be.checked");
@ -198,8 +198,10 @@ describe("Editor- CSA", () => {
cy.dragAndDropWidget("Button", 500, 300);
selectEvent("On click", "Control Component");
selectCSA("icon1", "Set Visibility");
cy.get('[data-cy="Value-fx-button"]').click();
cy.get('[data-cy="Value-input-field"]').clearAndTypeOnCodeMirror("{{false");
cy.get('[data-cy="event-Value-fx-button"]').click();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
"{{false"
);
// cy.get('[data-cy="Value-toggle-button"]')
// .should("be.visible")
// .and("not.be.checked");

View file

@ -33,7 +33,7 @@ import {
describe("Date Picker widget", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-datepicker-App`);
cy.openApp();
cy.dragAndDropWidget("Date Picker");
});
@ -60,21 +60,27 @@ describe("Date Picker widget", () => {
verifyAndModifyParameter(datePickerText.labelDefaultValue, data.date);
verifyComponentValueFromInspector(data.widgetName, data.date);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
verifyDate(data.widgetName, data.date);
data.date = randomDateOrTime();
selectAndVerifyDate(data.widgetName, data.date);
openEditorSidebar(data.widgetName);
verifyAndModifyParameter(datePickerText.labelformat, "DD/MM/YY");
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
verifyDate(data.widgetName, data.date, "DD/MM/YY");
verifyComponentValueFromInspector(data.widgetName, data.date);
cy.get(commonSelectors.canvas).click({ force: true });
openEditorSidebar(data.widgetName);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
// verifyDate(data.widgetName, "");
openEditorSidebar(data.widgetName);
verifyAndModifyToggleFx(
@ -90,7 +96,9 @@ describe("Date Picker widget", () => {
);
openEditorSidebar(data.widgetName);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
verifyDate(data.widgetName, datePickerText.defaultTime, "hh:mm A");
selectAndVerifyTime(data.widgetName, data.randomTime);
verifyAndModifyToggleFx(
@ -103,7 +111,9 @@ describe("Date Picker widget", () => {
"{{",
"[05-01]}}",
]); //WIP
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
@ -124,7 +134,9 @@ describe("Date Picker widget", () => {
datePickerText.noEventMessage
);
addDefaultEventHandler(data.alertMessage);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
data.date = randomDateOrTime();
selectAndVerifyDate(data.widgetName, data.date, "DD/MM/YY");
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
@ -202,7 +214,9 @@ describe("Date Picker widget", () => {
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.get(commonWidgetSelector.draggableWidget(datePickerText.datepicker1))
.find("input")
.should("have.css", "border-radius", "20px");

View file

@ -35,7 +35,7 @@ import {
describe("List view widget", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.apiCreateApp(`${fake.companyName}-Listview-App`);
cy.openApp();
cy.viewport(1200, 1200);
cy.dragAndDropWidget("List View", 50, 500);
@ -62,7 +62,7 @@ describe("List view widget", () => {
deleteInnerWidget(data.widgetName, commonWidgetText.button1);
deleteInnerWidget(data.widgetName, commonWidgetText.image1);
dropWidgetToListview("Text", 50, 50, data.widgetName);
dropWidgetToListview("Text", 100, 50, data.widgetName);
dropWidgetToListview("Text Input", 250, 50, data.widgetName);
addDataToListViewInputs(
@ -85,6 +85,7 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.waitForAppLoad();
cy.wait(2500);
cy.get(
@ -92,7 +93,9 @@ describe("List view widget", () => {
)
.realHover()
.realClick();
verifyAndModifyParameter("Text", codeMirrorInputLabel("listItem.name"));
cy.get(
'[data-cy="textcomponenttextinput-input-field"] '
).clearAndTypeOnCodeMirror(codeMirrorInputLabel("listItem.name"));
cy.forceClickOnCanvas();
cy.get(
`${commonWidgetSelector.draggableWidget(
@ -114,7 +117,8 @@ describe("List view widget", () => {
data.widgetName,
commonWidgetText.textinput1,
"value",
data.marks
data.marks,
true
);
verifyMultipleComponentValuesFromInspector(
@ -131,12 +135,14 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.waitForAppLoad();
cy.wait(2500);
cy.get(`${commonWidgetSelector.draggableWidget(data.widgetName)}:eq(0)`)
.realHover()
.click("topRight", { force: true });
openEditorSidebar(data.widgetName);
verifyAndModifyParameter(
listviewText.showBottomBorder,
codeMirrorInputLabel("false")
@ -163,7 +169,9 @@ describe("List view widget", () => {
`components.${data.widgetName}.selectedRow.${commonWidgetText.textinput1}.value`
)
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.get(`[data-cy=${data.widgetName.toLowerCase()}-row-1]`).click();
@ -230,12 +238,11 @@ describe("List view widget", () => {
).should("have.attr", "data-disabled", "true");
cy.get("[data-cy='disable-toggle-button']").click();
cy.get("[data-cy='border-radius-fx-button']:eq(0)").click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
// cy.get("[data-cy='border-radius-fx-button']:eq(0)").click();
cy.clearAndType('[data-cy="border-radius-input-field"]:eq(0)', "20");
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.get(
commonWidgetSelector.draggableWidget(listviewText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
@ -247,10 +254,11 @@ describe("List view widget", () => {
verifyAndModifyToggleFx(
commonWidgetText.parameterBoxShadow,
"0px 0px 0px 0px #00000040",
false,
false
);
cy.get('[data-cy="border-radius-fx-button"] > svg').click();
// cy.get('[data-cy="border-radius-fx-button"] > svg').click();
cy.get(commonWidgetSelector.boxShadowColorPicker).click();
fillBoxShadowParams(
@ -299,13 +307,16 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.waitForAppLoad();
cy.wait(3500);
cy.get(
`${commonWidgetSelector.draggableWidget(commonWidgetText.text1)}:eq(0)`
).click();
verifyAndModifyParameter("Text", codeMirrorInputLabel("listItem.name"));
cy.get(
'[data-cy="textcomponenttextinput-input-field"] '
).clearAndTypeOnCodeMirror(codeMirrorInputLabel("listItem.name"));
cy.forceClickOnCanvas();
cy.get(
`${commonWidgetSelector.draggableWidget(
@ -326,12 +337,14 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.waitForAppLoad();
cy.wait(2500);
cy.get(
`${commonWidgetSelector.draggableWidget(data.widgetName)}:eq(0)`
).click();
openEditorSidebar(data.widgetName);
verifyAndModifyParameter(
"Show bottom border",
codeMirrorInputLabel("false")
@ -349,7 +362,8 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.wait(2500);
cy.waitForAppLoad();
cy.wait(3500);
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionGenaral);
@ -375,13 +389,12 @@ describe("List view widget", () => {
)
).click();
cy.get("[data-cy='border-radius-fx-button']:eq(0)").click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
// cy.get("[data-cy='border-radius-fx-button']:eq(0)").click();
cy.clearAndType('[data-cy="border-radius-input-field"]:eq(0)', "20");
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.get(
commonWidgetSelector.draggableWidget(listviewText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
@ -393,6 +406,7 @@ describe("List view widget", () => {
verifyAndModifyToggleFx(
commonWidgetText.parameterBoxShadow,
"0px 0px 0px 0px #00000040",
false,
false
);

View file

@ -41,7 +41,7 @@ import {
describe("Modal", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-Modal-App`);
cy.openApp();
cy.dragAndDropWidget("Modal");
});
@ -67,11 +67,11 @@ describe("Modal", () => {
cy.get('[data-cy="modal-close-button"]').click();
cy.notVisible('[data-cy="modal-title"]');
openEditorSidebar("modal1", ["Options", "Properties", "Layout"]);
openEditorSidebar("modal1", ["Options", "Properties", "Devices"]);
editAndVerifyWidgetName(data.widgetName, [
"Options",
"Properties",
"Layout",
"Devices",
]);
verifyComponentFromInspector(data.widgetName);
@ -227,9 +227,9 @@ describe("Modal", () => {
commonWidgetText.codeMirrorLabelTrue
);
cy.get('[data-cy="modal-close-button"]').click();
cy.get(commonWidgetSelector.draggableWidget("modal1")).should(
"not.be.visible"
);
cy.get(commonWidgetSelector.draggableWidget("modal1"))
.find("button")
.should("not.be.visible");
cy.get(commonWidgetSelector.parameterTogglebutton("Visibility")).click();
verifyAndModifyToggleFx(

View file

@ -41,7 +41,7 @@ import {
describe("Multiselect widget", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-Multiselect-App`);
cy.openApp();
cy.dragAndDropWidget(multiselectText.multiselect);
});
@ -66,7 +66,9 @@ describe("Multiselect widget", () => {
"General",
]);
verifyAndModifyParameter(commonWidgetText.parameterLabel, data.label);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.get(multiselectSelector.multiselectLabel(data.widgetName)).should(
"have.text",
data.label
@ -77,7 +79,9 @@ describe("Multiselect widget", () => {
commonWidgetText.labelDefaultValue,
codeMirrorInputLabel("[1,2,3]")
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
verifyMultiselectHeader(
data.widgetName,
multiselectText.labelAllItemsSelected
@ -147,7 +151,9 @@ describe("Multiselect widget", () => {
multiselectText.noEventsMessage
);
addDefaultEventHandler(data.alertMessage);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
selectFromMultiSelect(data.widgetName, ["", "", "true"]);
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
@ -214,7 +220,9 @@ describe("Multiselect widget", () => {
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.get(
commonWidgetSelector.draggableWidget(multiselectText.defaultWidgetName)
)
@ -363,7 +371,7 @@ describe("Multiselect widget", () => {
);
});
it.only("should verify CSA", () => {
it("should verify CSA", () => {
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget("Number input", 600, 50);
selectEvent("On change", "Control Component");

View file

@ -44,7 +44,7 @@ describe("Number Input", () => {
cy.apiDeleteApp();
});
itß("should verify the properties of the number input widget", () => {
it("should verify the properties of the number input widget", () => {
const data = {};
data.widgetName = fake.widgetName;
data.tooltipText = fake.randomSentence;
@ -239,10 +239,7 @@ describe("Number Input", () => {
cy.clearAndType(commonWidgetSelector.draggableWidget(data.widgetName), "1");
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
`Minimum value is ${data.minimumLength}`
);
).verifyVisibleElement("have.text", `Minimum value is 2`);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
"13"
@ -315,7 +312,7 @@ describe("Number Input", () => {
cy.get('[data-cy="togglr-button-left"]').click();
verifyAlignment(numberInputText.defaultWidgetName, "sideLeft");
addCustomWidthOfLabel("50");
verifyCustomWidthOfLabel(numberInputText.defaultWidgetName, "50");
verifyCustomWidthOfLabel(numberInputText.defaultWidgetName, "35");
selectColourFromColourPicker(
"Text",
data.labelColor,
@ -331,15 +328,15 @@ describe("Number Input", () => {
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(3500);
verifyWidgetColorCss(
`[data-cy="label-${numberInputText.defaultWidgetName}"]>label`,
"color",
data.labelColor,
true
);
verifyAlignment(numberInputText.defaultWidgetName, "sideLeft");
verifyCustomWidthOfLabel(numberInputText.defaultWidgetName, "50");
verifyCustomWidthOfLabel(numberInputText.defaultWidgetName, "35");
verifyInputFieldColors("numberinput1", data);
verifyBoxShadowCss(
@ -364,6 +361,7 @@ describe("Number Input", () => {
verifyCSA(data);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(3500);
verifyCSA(data);
});
});

View file

@ -311,7 +311,7 @@ describe("Password Input", () => {
cy.get('[data-cy="togglr-button-left"]').click();
verifyAlignment(passwordInputText.defaultWidgetName, "sideLeft");
addCustomWidthOfLabel("50");
verifyCustomWidthOfLabel(passwordInputText.defaultWidgetName, "50");
verifyCustomWidthOfLabel(passwordInputText.defaultWidgetName, "35");
selectColourFromColourPicker(
"Text",
data.labelColor,
@ -335,7 +335,7 @@ describe("Password Input", () => {
);
verifyAlignment(passwordInputText.defaultWidgetName, "sideLeft");
verifyCustomWidthOfLabel(passwordInputText.defaultWidgetName, "50");
verifyCustomWidthOfLabel(passwordInputText.defaultWidgetName, "35");
verifyInputFieldColors("passwordinput1", data);
verifyBoxShadowCss(

View file

@ -57,7 +57,7 @@ import { waitForQueryAction } from "Support/utils/queries";
describe("Table", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.apiCreateApp(`${fake.companyName}-table-App`);
cy.openApp();
deleteDownloadsFolder();
cy.viewport(1400, 2200);
@ -372,6 +372,7 @@ describe("Table", () => {
deleteAndVerifyColumn("id");
deleteAndVerifyColumn("name");
deleteAndVerifyColumn("email");
deleteAndVerifyColumn("fake-link");
cy.wait(500);
addAndOpenColumnOption("Fake-String", `string`);
selectDropdownOption('[data-cy="input-overflow"] >>:eq(0)', `wrap`);
@ -388,10 +389,13 @@ describe("Table", () => {
.eq(0)
.should("have.css", "background-color", "rgb(255, 255, 0)", {
timeout: 10000,
})
});
cy.get(tableSelector.column(0))
.eq(0)
.find(".align-items-center")
.last()
.should("have.css", "color", "rgb(62, 82, 91)")
.and("have.text", "Sarah");
.should("have.text", "Sarah")
.and("have.css", "color", "rgb(255, 0, 0)");
cy.get('[data-cy="make-editable-toggle-button"]').click();
cy.get('[data-cy="header-validation"]').verifyVisibleElement(
@ -678,7 +682,9 @@ describe("Table", () => {
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
openEditorSidebar(tableText.defaultWidgetName);
openAccordion("Columns");
deleteAndVerifyColumn("email");
@ -698,7 +704,9 @@ describe("Table", () => {
"Border radius",
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({
force: true,
});
cy.get(
commonWidgetSelector.draggableWidget(tableText.defaultWidgetName)
@ -712,7 +720,8 @@ describe("Table", () => {
commonWidgetText.parameterBoxShadow,
commonWidgetText.boxShadowDefaultValue,
false,
"0px 0px 0px 0px "
"0px 0px 0px 0px ",
false
);
cy.get(commonWidgetSelector.boxShadowColorPicker).click();
@ -737,6 +746,7 @@ describe("Table", () => {
"have.text",
"Table type"
);
cy.get('[data-cy="dropdown-table-type"]').realHover();
cy.get('[data-cy="table-type-fx-button"] > svg').click();
cy.get('[data-cy="table-type-input-field"]').clearAndTypeOnCodeMirror(
`randomText`
@ -1095,7 +1105,7 @@ describe("Table", () => {
cy.get(".tooltip-inner").invoke("hide");
verifyNodeData("components", "Object", "9 entries ");
openNode("components");
verifyNodeData(tableText.defaultWidgetName, "Object", "23 entries ");
verifyNodeData(tableText.defaultWidgetName, "Object", "27 entries ");
openNode(tableText.defaultWidgetName);
verifyNodeData("newRows", "Array", "0 item ");
@ -1232,7 +1242,10 @@ describe("Table", () => {
cy.get('[data-cy="inspector-close-icon"]').click();
cy.dragAndDropWidget("Text", 800, 200);
openEditorSidebar(commonWidgetText.text1);
verifyAndModifyParameter("Text", "Column Email");
cy.get(
'[data-cy="textcomponenttextinput-input-field"]'
).clearAndTypeOnCodeMirror("Column Email");
// verifyAndModifyParameter("Text", "Column Email");
cy.get('[data-cy="inspector-close-icon"]').click();
cy.get(`[data-cy="draggable-widget-${commonWidgetText.text1}"]`).should(
"have.text",

View file

@ -19,14 +19,14 @@ import {
selectCSA,
selectEvent,
} from "Support/utils/events";
import { randomString } from "Support/utils/textInput";
import { randomString } from "Support/utils/editor/textInput";
import { buttonText } from "Texts/button";
describe("Text Input", () => {
const data = {};
beforeEach(() => {
cy.viewport(1200, 1200);
data.appName = `${fake.companyName}-text-App1`;
data.appName = `${fake.companyName}-text-App`;
cy.apiLogin();
cy.apiCreateApp(data.appName);
cy.openApp();
@ -56,7 +56,7 @@ describe("Text Input", () => {
).verifyVisibleElement("have.text", "Cypress testing text component");
});
it.only("should verify styles of text component", () => {
it("should verify styles of text component", () => {
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
selectFromSidebarDropdown("Weight", "bolder");
selectFromSidebarDropdown("Font variant", "initial");
@ -90,7 +90,7 @@ describe("Text Input", () => {
it("should verify preview of text component", () => {});
it("should verify CSA", () => {
it.only("should verify CSA", () => {
const data = {};
data.customText = randomString(12);
@ -120,13 +120,13 @@ describe("Text Input", () => {
"not.be.visible"
);
});
it("should verify expossed values", () => {
it.only("should verify expossed values", () => {
cy.get(commonWidgetSelector.sidebarinspector).click();
verifyNodeData("components", "Object", "1 entry ");
openNode("components");
openNode("text1");
verifyValue("text", "String", `"Hello World👋"`);
verifyValue("text", "String", `"Hello The👋"`);
verifyValue("isVisible", "Boolean", "true");
verifyValue("isLoading", "Boolean", "false");
verifyValue("isDisabled", "Boolean", "false");
@ -134,8 +134,8 @@ describe("Text Input", () => {
verifyfunctions("clear", "Function");
verifyfunctions("setText", "Function");
verifyfunctions("visibility", "Function");
verifyfunctions("setDisabled", "Function");
verifyfunctions("setDisable", "Function");
verifyfunctions("setVisibility", "Function");
verifyfunctions("setLoadingState", "Function");
verifyfunctions("setLoading", "Function");
});
});

View file

@ -111,7 +111,9 @@ describe("Text Input", () => {
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]);
addDefaultEventHandler(data.customText);
cy.wait(1000);
cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}");
cy.wait("@events");
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
@ -268,7 +270,7 @@ describe("Text Input", () => {
data.errorTextColor = fake.randomRgba;
data.iconColor = fake.randomRgba;
data.labelColor = fake.randomRgba;
ata.widgetName = textInputText.defaultWidgetName;
data.widgetName = textInputText.defaultWidgetName;
openEditorSidebar(textInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
@ -311,7 +313,7 @@ describe("Text Input", () => {
cy.get('[data-cy="togglr-button-left"]').click();
verifyAlignment(textInputText.defaultWidgetName, "sideLeft");
addCustomWidthOfLabel("50");
verifyCustomWidthOfLabel(textInputText.defaultWidgetName, "50");
verifyCustomWidthOfLabel(textInputText.defaultWidgetName, "35");
selectColourFromColourPicker(
"Text",
data.labelColor,
@ -335,7 +337,7 @@ describe("Text Input", () => {
);
verifyAlignment(textInputText.defaultWidgetName, "sideLeft");
verifyCustomWidthOfLabel(textInputText.defaultWidgetName, "50");
verifyCustomWidthOfLabel(textInputText.defaultWidgetName, "35");
verifyInputFieldColors("textinput1", data);
verifyBoxShadowCss(

View file

@ -25,11 +25,12 @@ describe("App Export Functionality", () => {
let currentVersion = "";
let otherVersions = [];
beforeEach(() => {
cy.appUILogin();
cy.apiLogin();
});
it("Verify the elements of export dialog box", () => {
cy.createApp(data.appName1);
cy.apiCreateApp(data.appName1);
cy.openApp();
cy.dragAndDropWidget(buttonText.defaultWidgetText);
cy.get(appVersionSelectors.appVersionLabel).should("be.visible");
cy.renameApp(data.appName1);
@ -55,6 +56,7 @@ describe("App Export Functionality", () => {
});
it("Verify 'Export app' functionality of an application", () => {
cy.visit("/");
cy.get(commonSelectors.appHeaderLable).should("be.visible");
selectAppCardOption(

View file

@ -28,7 +28,7 @@ describe("App Import Functionality", () => {
let exportedFilePath;
beforeEach(() => {
cy.appUILogin();
cy.apiLogin();
});
before(() => {
cy.fixture("templates/test-app.json").then((app) => {
@ -39,6 +39,7 @@ describe("App Import Functionality", () => {
});
});
it("Verify the Import functionality of an Application", () => {
cy.visit("/");
cy.get("body").then(($title) => {
if ($title.text().includes(commonText.welcomeTooljetWorkspace)) {
cy.get(dashboardSelector.importAppButton).click();
@ -73,6 +74,7 @@ describe("App Import Functionality", () => {
.and("have.text", importText.appImportedToastMessage);
cy.get(".driver-close-btn").click();
cy.wait(500);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
appData.name.toLowerCase()

View file

@ -105,10 +105,8 @@ export const verifyBasicData = (widgetName, data) => {
cy.get(commonWidgetSelector.draggableWidget(widgetName)).click({
force: true,
});
cy.wait(500);
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
cy.get(commonWidgetSelector.draggableWidget("textinput1")).should(
cy.get(`[data-cy='input-textinput1']`).should(
"have.value",
data.customMessage
);

View file

@ -40,13 +40,16 @@ export const openEditorSidebar = (widgetName = "") => {
export const verifyAndModifyToggleFx = (
paramName,
defaultValue,
toggleModification = true
toggleModification = true,
hiddenFx = true
) => {
cy.get(commonWidgetSelector.parameterLabel(paramName)).should(
"have.text",
paramName
);
cy.get(commonWidgetSelector.parameterTogglebutton(paramName)).realHover();
if (hiddenFx) {
cy.get(commonWidgetSelector.parameterTogglebutton(paramName)).realHover();
}
cy.get(commonWidgetSelector.parameterFxButton(paramName, " > svg")).click();
if (defaultValue)
cy.get(commonWidgetSelector.parameterInputField(paramName))
@ -61,11 +64,14 @@ export const addDefaultEventHandler = (message) => {
cy.get(commonWidgetSelector.addEventHandlerLink)
.should("contain.text", commonWidgetText.addEventHandlerLink)
.click();
cy.intercept("PUT", "events").as("events");
cy.get(commonWidgetSelector.eventHandlerCard).click();
cy.wait(1000);
cy.get(commonWidgetSelector.alertMessageInputField)
.find('[data-cy*="-input-field"]')
.eq(0)
.clearAndTypeOnCodeMirror(message);
cy.get('[data-cy="run-only-if-input-field"]').click({ force: true });
};
export const addAndVerifyTooltip = (widgetSelector, message) => {
@ -94,6 +100,7 @@ export const verifyComponentValueFromInspector = (
value,
openStatus = "closed"
) => {
cy.wait(3000);
cy.get(commonWidgetSelector.sidebarinspector).click();
if (openStatus == "closed") {
cy.get(commonWidgetSelector.inspectorNodeComponents).click();
@ -359,7 +366,9 @@ export const addTextWidgetToVerifyValue = (customfunction) => {
cy.forceClickOnCanvas();
cy.dragAndDropWidget("Text", 600, 80);
openEditorSidebar("text1");
verifyAndModifyParameter("Text", codeMirrorInputLabel(customfunction));
cy.get(
'[data-cy="textcomponenttextinput-input-field"] '
).clearAndTypeOnCodeMirror(codeMirrorInputLabel(customfunction));
cy.forceClickOnCanvas();
cy.waitForAutoSave();
};

View file

@ -41,10 +41,13 @@ export const randomString = (length) => {
};
export const verifyCSA = (data) => {
cy.clearAndType(
commonWidgetSelector.draggableWidget("textinput1"),
data.customText
);
cy.get(commonWidgetSelector.draggableWidget("textinput1")).click({
force: true,
});
cy.get(commonWidgetSelector.draggableWidget("textinput1"))
.find("input")
.clear()
.type(data.customText);
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", data.customText);

View file

@ -35,7 +35,6 @@ export const selectCSA = (
.type(`{selectAll}{backspace}${componentAction}{enter}`);
cy.wait("@events");
cy.get('[data-cy="debounce-input-field"]')
.eq(1)
.click()
.type(`{selectAll}{backspace}${debounce}{enter}`);
cy.wait("@events");

View file

@ -84,11 +84,21 @@ export const addDataToListViewInputs = (listviewName, childName, data) => {
});
};
export const verifyValuesOnList = (listviewName, childName, type, value) => {
export const verifyValuesOnList = (
listviewName,
childName,
type,
value,
isChild = false
) => {
cy.get(commonWidgetSelector.draggableWidget(listviewName)).within(() => {
cy.get(commonWidgetSelector.draggableWidget(childName)).each(
($element, i) => {
cy.wrap($element).should(`have.${type}`, value[i]);
if (isChild) {
cy.wrap($element).find("input").should(`have.${type}`, value[i]);
} else {
cy.wrap($element).should(`have.${type}`, value[i]);
}
}
);
});

View file

@ -52,6 +52,7 @@ export const addAndVerifyColor = (
};
export const typeOnFx = (fx, data) => {
cy.get(commonWidgetSelector.parameterTogglebutton(fx)).realHover();
cy.get(commonWidgetSelector.parameterFxButton(fx)).eq(0).realClick();
cy.get(commonWidgetSelector.parameterInputField(fx)).clearAndTypeOnCodeMirror(
data

View file

@ -136,12 +136,16 @@ export const verifyAndModifyToggleFx = (
paramName,
defaultValue,
toggleModification = true,
helper = ""
helper = "",
hiddenFx = true
) => {
cy.get(`[data-cy="label-${cyParamName(paramName)}"]`).should(
"have.text",
paramName
);
if (hiddenFx) {
cy.get(commonWidgetSelector.parameterTogglebutton(paramName)).realHover();
}
cy.get(commonWidgetSelector.parameterFxButton(paramName, "> svg"))
.scrollIntoView()
.click();

View file

@ -96,6 +96,8 @@ The dropdown will display all the apps associated with your account. Select an a
The file will contain all the data from audit logs. The log file can be created by specifying the path in the [environment variables](/docs/setup/env-vars). The log file is rotated on a daily basis and is updated dynamically every time a new audit log is generated.
Learn more about **setting up the log file generation** [here](/docs/how-to/setup-rsyslog).
#### Log Rotation
The log file is configured to rotate on a daily basis. This means that a new log file will be created every day, ensuring efficient management and organization of audit data.

View file

@ -11,27 +11,21 @@ With this feature, you gain the ability to rebrand the following key elements:
- **Application Logo**: This includes the logo displayed on the login screen, dashboard, and app-editor.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/white-label/newdash.png" alt="ToolJet - Enterprise - White label"/>
</div>
<div style={{textAlign: 'center'}}>
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/enterprise/white-label/newdash.png" alt="ToolJet - Enterprise - White label" />
</div>
- **Favicon**: The small icon associated with your application.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/white-label/newfav.png" alt="ToolJet - Enterprise - White label"/>
</div>
<div style={{textAlign: 'center'}}>
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/enterprise/white-label/newfav.png" alt="ToolJet - Enterprise - White label" />
</div>
- **Page Title**: This is the text displayed next to the Favicon.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/white-label/title.png" alt="ToolJet - Enterprise - White label" />
</div>
<div style={{textAlign: 'center'}}>
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/enterprise/white-label/title.png" alt="ToolJet - Enterprise - White label" />
</div>
## Configuration
@ -42,7 +36,22 @@ To enable white labelling, you'll need to go to the **Settings** from the Dashbo
- **Page Title**: Enter the text you want to display as your application's title. Preferred title length are 50-60 characters.
<div style={{textAlign: 'center'}}>
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/enterprise/white-label/whitelabelsettings.png" alt="ToolJet - Enterprise - White label" />
</div>
<img className="screenshot-full" src="/img/enterprise/white-label/whitelabelsettings.png" alt="ToolJet - Enterprise - White label" />
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## ToolJet Cloud
If you are using ToolJet Cloud, you can enable white labelling by going to the **Settings** from the Dashboard and clicking on the `White labelling` tab. On the White labelling page, you'll be able to configure the following:
- **Application Logo**: Add the URL of the image you want to use as your application logo. Preferred dimensions of the logo are: width `130px` and height `26px`.
- **Page Title**: Enter the text you want to display as your application's title. Preferred title length are 50-60 characters.
- **Favicon**: Enter the URL of the image you want to use as your application's favicon. Preferred dimensions of the favicon are: width `32px` and height `32px` or `16px` and height `16px`.
<div style={{textAlign: 'center'}}>
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/enterprise/white-label/whitecloud.png" alt="Whitelabel Cloud" />
</div>
</div>
</div>

View file

@ -0,0 +1,46 @@
---
id: importing-exporting-applications
title: Importing and Exporting Applications
---
This documentation explains the process of exporting and importing applications in ToolJet.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## 1. Exporting Applications
- Navigate to the dashboard.
- Click on the settings icon located in the top right corner of the application.
- Click on the **Export app** button.
<div style={{textAlign: 'center', marginBottom:'15px'}}>
<img className="screenshot-full" src="/img/v2-beta/app-builder/import-export-apps/export-app-button.png" alt="Export App Button" />
</div>
- If you select `Export All`, all the versions of the application will be exported in JSON format. If you select `Export selected version`, only the selected version will be exported in JSON format.
- Ticking the `Export ToolJet table schema` checkbox will also export the related ToolJet Database table schemas with your application. In this case, when you import the application in a workspace, the related ToolJet Database tables will also be created.
<div style={{textAlign: 'center', marginBottom:'15px'}}>
<img className="screenshot-full" src="/img/v2-beta/app-builder/import-export-apps/export-options.png" alt="Export App Options" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## 2. Importing Applications
- Navigate to the dashboard.
- Click on the ellipses on the **Create new app** button and select `Import`.
<div style={{textAlign: 'center', marginBottom:'15px'}}>
<img className="screenshot-full" src="/img/v2-beta/app-builder/import-export-apps/import-button.png" alt="Import App Button" />
</div>
- After clicking on `Import`, choose the relevant JSON file that you previously downloaded during the application export process.
<div style={{textAlign: 'center', marginBottom:'15px'}}>
<img className="screenshot-full" src="/img/v2-beta/app-builder/import-export-apps/select-app-to-import.png" alt="Select App To Import" />
</div>
</div>

View file

@ -48,7 +48,7 @@ The use of this environment variable facilitates plugin development by enabling
Please note that the marketplace is not enabled by default. After updating the variable, restart your ToolJet instance.
For information on running ToolJet on your local machine, please refer to the instructions provided **[here](/docs/category/contributing-guide)**. You can access the marketplace by navigating to the **'/integrations'** route.
For information on running ToolJet on your local machine, please refer to the instructions provided **[here](/docs/contributing-guide/setup/architecture)**. You can access the marketplace by navigating to the **'/integrations'** route.
### Step 3: Installation of tooljet-cli

View file

@ -17,14 +17,14 @@ Follow these steps to setup and run ToolJet on macOS for development purposes. O
```bash
/bin/bash -c "(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
```
1.2 Install Node.js ( version: v18.18.2 ) and npm (version: v9.8.2)
1.2 Install Node.js ( version: v18.18.2 ) and npm (version: v9.8.1)
```bash
brew install nvm
export NVM_DIR=~/.nvm
source $(brew --prefix nvm)/nvm.sh
nvm install 18.18.2
nvm use 18.18.2
npm install -g npm@9.8.2
npm install -g npm@9.8.1
```
1.3 Install Postgres

View file

@ -5,9 +5,13 @@ title: Airtable
# Airtable
ToolJet can connect to your Airtable account to read and write data.Airtable Personal Access Token is required to connect to the Airtable data source on ToolJet. You can generate the PAT by visiting [Developer Hub from your Airtable profile](https://support.airtable.com/docs/creating-and-using-api-keys-and-access-tokens#understanding-personal-access-token-basic-actions).
ToolJet can connect to your Airtable account to read and write data. **Personal Access Token** is required to connect to the Airtable data source on ToolJet. You can generate the Personal Access Token by visiting [Developer Hub from your Airtable profile](https://support.airtable.com/docs/creating-and-using-api-keys-and-access-tokens#understanding-personal-access-token-basic-actions).
<img className="screenshot-full" src="/img/datasource-reference/airtable/add_creds.gif" alt="irtable record"/>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/datasource-reference/airtable/airtableconnect.gif" alt="Airtable Data Source Connection" />
</div>
<br/>
:::info
Airtable API has a rate limit, and at the time of writing this documentation, the limit is five(5) requests per second per base. You can read more about rate limits here **[Airtable API](https://airtable.com/api)**.
@ -15,27 +19,27 @@ Airtable API has a rate limit, and at the time of writing this documentation, th
## Supported Operations
- **[Listing records](#listing-records)**
- **[Retrieving a record](#retrieving-a-record)**
- **[Creating a record](#creating-a-record)**
- **[Updating a record](#updating-a-record)**
- **[Deleting a record](#deleting-a-record)**
- **[List records](#list-records)**
- **[Retrieve record](#retrieve-record)**
- **[Create record](#create-record)**
- **[Update record](#update-record)**
- **[Delete record](#delete-record)**
### Listing records
### List records
This query lists all the records in a table. The results are paginated and each page can have up to 100 records.
This operation returns a list of records from the specified table.
#### Required parameters:
- **Base ID:** To find the Base ID, first visit **airtable.com/api**. Select from the list of bases the base whose ID you'd like to find out. Example Base ID: `appDT3UCPffPiSmFd`
- **Table name:** Enter the table name whose data you want to fetch.
- **Base ID:** To find the Base ID, first visit **airtable.com/api**. Then select the base you want to connect to. The Base ID will be mentioned in the API documentation. Example Base ID: `appDT3UCPffPiSmFd`
- **Table name:** The name of the table from which you want to fetch the records.
#### Optional parameters:
- **Page size:** The number of records returned in each request. Must be less than or equal to 100. Default is 100.
- **offset:** If there are more records, the response will contain an offset. To fetch the next page of records, include offset in the next request's parameters.
- **Page size:** The number of records returned in each request. Default is 100 records.
<img className="screenshot-full" src="/img/datasource-reference/airtable/listv2.png" alt="List airtable record" />
- **offset:** The offset value is used to fetch the next set of records. The offset value is returned in the response of the previous request.
Example response from Airtable:
@ -71,15 +75,16 @@ Example response from Airtable:
}
```
### Retrieving a record
### Retrieve record
#### Required parameters:
- **Base ID**
- **Table name**
- **Record ID**
- **Base ID**: To find the Base ID, first visit **airtable.com/api**. Then select the base you want to connect to. The Base ID will be mentioned in the API documentation. Example Base ID: `appDT3UCPffPiSmFd`
- **Table name**: The name of the table from which you want to fetch the records.
- **Record ID**: The ID of the record you want to retrieve.
<img className="screenshot-full" src="/img/datasource-reference/airtable/retv2.png" alt="Retrieve airtable record" />
Example response from Airtable:
@ -94,42 +99,36 @@ Example response from Airtable:
}
```
### Creating a record
### Create record
#### Required parameters:
- **Base ID**
- **Table name**
- **Records**
- **Base ID**: To find the Base ID, first visit **airtable.com/api**. Then select the base you want to connect to. The Base ID will be mentioned in the API documentation. Example Base ID: `appDT3UCPffPiSmFd`
<img className="screenshot-full" src="/img/datasource-reference/airtable/createv2.png" alt="Create airtable record" />
- **Table name**: The name of the table from which you want to fetch the records.
#### Example Records:
- **Records**: The records you want to create. The records should be in the form of an array of objects. Each object should have a `fields` key, which contains the fields of the record. The field names should be the same as the field names in the Airtable table.
```json
[
{
"fields": {
"Notes": "sdfdsf",
"Name": "dsfdsf"
**Example creating two records:**
```json title="Records"
[
{
"fields": {
"Notes": "sdfdsf",
"Name": "dsfdsf"
}
},
{
"fields": {
"Notes": "note1",
"Name": "dsfdsf"
}
}
},
{
"fields": {
"Notes": "note1",
"Name": "dsfdsf"
}
}
]
```
]
```
Click on the `run` button to run the query.
:::info
NOTE: Query must be saved before running.
:::
Example response from Airtable:
Query returns the following response when the records are created successfully:
```json
{
@ -154,31 +153,28 @@ Example response from Airtable:
}
```
### Updating a record
### Update record
#### Required parameters:
- **Base ID**
- **Table name**
- **Record ID**
- **Base ID**: To find the Base ID, first visit **airtable.com/api**. Then select the base you want to connect to. The Base ID will be mentioned in the API documentation. Example Base ID: `appDT3UCPffPiSmFd`
<img className="screenshot-full" src="/img/datasource-reference/airtable/updv2.png" alt="Update airtable record"/>
- **Table name**: The name of the table from which you want to fetch the records.
#### Example body:
- **Record ID**: The ID of the record you want to update.
<div style={{textAlign: 'center'}}>
- **Body**: The fields you want to update. The fields should be in the form of an object. The field names should be the same as the field names in the Airtable table.
<img className="screenshot-full" src="/img/datasource-reference/airtable/airtable-update-example-body.png" alt="Airtable update body" />
**Example updating a record:**
```json title="Body"
{
"Notes": "Example Notes",
"Name": "change"
}
```
</div>
Click on the `run` button to run the query.
:::info
NOTE: Query must be saved before running.
:::
Example response from Airtable:
Query returns the following response when the record is updated successfully:
```json
{
@ -191,7 +187,7 @@ Example response from Airtable:
}
```
### Deleting a record
### Delete record
#### Required parameters:
@ -199,15 +195,7 @@ Example response from Airtable:
- **Table name**
- **Record ID**
<img className="screenshot-full" src="/img/datasource-reference/airtable/delv2.png" alt="Delete airtable record" />
Click on the `run` button to run the query.
:::info
NOTE: Query must be saved before running.
:::
Example response from Airtable:
Query returns the following response when the record is deleted successfully:
```json
{

View file

@ -48,10 +48,10 @@ To create a query for sending an email, follow these steps:
- **Body** : You can enter the body text of the email in either raw text or html format, in their respective fields.
- **Attachments** : You can add attachments to an SMTP query by referencing the file from the File Picker component in the attachments field.
For instance, you can set the `Attachments` field value to `{{ components.filepicker1.file }}` or pass an array of `{{ name: 'filename.jpg', dataURL: '......' }}` objects to include attachments.
For instance, you can set the `Attachments` field value to `{{ components.filepicker1.file }}` or pass an object `{{ name: 'filename.jpg', dataURL: '......' }}` to include attachments.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/smtp/querysmtp.png" alt="smtp connect" />
</div>
</div>

View file

@ -79,11 +79,11 @@ Now, create a new table in **[ToolJets Database](/docs/tooljet-database/)** t
### 3. Integrate Data
To display employees in the application, we first need to fetch data from the database using a query:
- Click on the Add button in the **[Query Panel](/docs/app-builder/query-panel/)**, select ToolJet Database
- Rename the query to `getEmployees`
- Choose `employees` as Table name, List rows as Operations
- Toggle Run this query on application load? to automatically run the query when the app starts
- Click on Run to fetch data
- Click on the Add button in the **[Query Panel](/docs/app-builder/query-panel/)**, select ToolJet Database.
- Rename the query to `getEmployees`.
- Choose `employees` as Table name, List rows as Operations.
- Toggle Run this query on application load? to automatically run the query when the app starts.
- Click on Run to fetch data.
<div style={{marginBottom:'15px', height:'397px', }}>
<iframe
@ -147,10 +147,10 @@ Now the Table component is filled with the data returned by the `getEmployees` q
Next step is to create a way to add data for new employees.
- Click on Add in the query panel, select ToolJet Database
- Select `employees` as Table name, Create row as Operations
- Rename the query to `addEmployee`
- Click Add Column to add required columns
- Click on Add in the query panel, select ToolJet Database.
- Select `employees` as Table name, Create row as Operations.
- Rename the query to `addEmployee`.
- Click Add Column to add required columns.
- Enter code below for **email** and **firstname** column keys:
```js
@ -167,9 +167,9 @@ Frame all the remaining keys in the same format.
Let's continue working on this query. The data needs to reload once this query runs since we want the Table component to be populated with the updated data. Follow the below steps to run the `getEmployees` query after the `addEmployee` query is completed.
- Scroll down and click on New event handler
- Select Query Success as Event and Run Query as Action
- Select `getEmployees` as Query
- Scroll down and click on New event handler.
- Select Query Success as Event and Run Query as Action.
- Select `getEmployees` as Query.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px'}} className="screenshot-full" src="/img/quickstart-guide/reload-data-v2.png" alt="Reload Table Data" />
@ -178,9 +178,9 @@ Let's continue working on this query. The data needs to reload once this query r
We are now ready with a query that will allow us to add new employee data. Let's link this query to a button.
In the bottom-right corner of the Table component, there is a `+`/Add new row button. Follow the below steps to run the `addEmployee` query on click of the `+`/Add new row button:
- Click on the Table component, go to Events in configuration panel and add a New event handler
- Choose Add new rows as Event, Run Query as Action
- Select `addEmployee` as the Query
- Click on the Table component, go to Events in configuration panel and add a New event handler.
- Choose Add new rows as Event, Run Query as Action.
- Select `addEmployee` as the Query.
<div style={{marginBottom:'15px', height:'397px', }}>
<iframe
@ -220,7 +220,7 @@ The preview, release and share buttons are on the top-right of the App-Builder.
- Click on the Preview on the top-right of app builder to review how your application is coming along while development.
- Once the development is done and you are ready to use the application, click on Release button to deploy the app.
- Finally, share your application with your end users using Share button
- Finally, share your application with your end users using Share button.
<div style={{textAlign: 'center'}}>

View file

@ -3,6 +3,9 @@ id: gitsync
title: GitSync
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<div className='badge badge--primary heading-badge'>Available on: Paid plans</div>
@ -28,11 +31,17 @@ Facilitating the movement of applications across different ToolJet deployments (
</div>
<br/>
<Tabs>
<TabItem value="GitHub" label="Setting up GitSyncing with GitHub">
## Setting up GitSyncing with GitHub
:::caution
- ToolJet support git repo managers like GitHub, GitLab, Bitbucket, AWS CodeCommit, and Azure Repos.
- Only Admins have the permission to configure the GitSync feature on workspace level.
- The default branch name for the git repository should be `master`.
:::
### Step 1: Create a new repository on GitHub
@ -252,4 +261,276 @@ You can check for updates in the git repository by clicking on the **GitSync** b
<img className="screenshot-full" src="/img/gitsync/updatecheck.png" alt="GitSync" />
</div>
</div>
</TabItem>
<TabItem value="GitLab" label="Setting up GitSyncing with GitLab">
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Setting up GitSyncing with GitLab
:::caution
- ToolJet support git repo managers like GitHub, GitLab, Bitbucket, AWS CodeCommit, and Azure Repos.
- Only Admins have the permission to configure the GitSync feature on workspace level.
- The default branch name for the git repository should be `master`.
:::
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Step 1: Create a new repository
Create a new repository on GitLab. The repository can be public or private. You can also use an existing repository. **Make sure that the repository is empty**.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/repo.png" alt="GitLab Repo" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Step 2: Obtain the repository URL
Obtain the **SSH URL** of the repository. On GitLab, you can obtain the URL by clicking on the **Clone** button and selecting the **SSH** option.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/gitlabssh.png" alt="GitLab SSH" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Step 3: Configure the GitSync feature on ToolJet
Go to the **Workspace settings**, and click on the **Configure git** tab.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitsync.png" alt="GitLab SSH Key" />
</div>
Enter the **SSH URL** of the repository (obtained in Step 2) in the **Git repository URL** field. Click on the **Generate SSH key** button, and copy the SSH key that is generated. The SSH key is used to authenticate ToolJet with the gitlab repository.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/sshkey.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Step 4: Deploy the SSH key to GitLab repository
From the top-left corner, click on the user avatar and select the **Edit Profile** option. Navigate to the **SSH Keys** tab and click on the **Add new key** button.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/addingssh.png" alt="GitLab SSH Key" />
</div>
Paste the SSH key that you copied in Step 3 in the **Key** field, enter a title for the SSH key in the **Title** field, set **Usage type** to **Authenticatioin & signing**, and set the ***Expiration date(optional)**. Finally, click on the **Add key** button.
<!--
Enter a title for the SSH key in the **Title** field. Paste the SSH key that you copied in Step 3 in the **Key** field. Make sure that the **Allow write access** checkbox is checked, especially when configuring the GitSync feature to [push changes to Git](#pushing-changes-to-git-repo). However, it is not mandatory to check this option when setting up the GitSync feature for [pulling changes from Git](#pulling-changes-from-git-repo). Finally, click on the **Add key** button. -->
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/activessh.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Step 5: Finish the GitSync configuration on ToolJet
Go back to the **Configure git** tab on ToolJet, and click on the **Finalize setup** button. If the SSH key is configured correctly, you will see a success message.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/configfin.png" alt="GitLab SSH Key" />
</div>
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Enable/Disable GitSync
To enable or disable the GitSync feature, go to the **Configure git** tab on the **Workspace settings** page, and toggle on/off the **Connect** switch. This is only available if the GitSync feature is configured.
**When enabled**
On clicking the GitSync button, the users will be able to commit changes to the git repository.
**When disabled**
1. For non-admin users: The users will not be able to commit changes to the git repository. They will see a dialogue box that the GitSync feature is not configured and they need to contact the admin to configure it.
2. For admin users: The users will see a dialogue box with a link to configure the GitSync feature.
<br/>
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/connect.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Delete GitSync configuration
To delete the GitSync configuration, go to the **Configure git** tab on the **Workspace settings** page, and click on the **Delete configuration** button. This will delete the SSH key from the ToolJet configuration and the GitSync feature will be disabled.
**Note:** Deleting the GitSync configuration will not delete the apps from the git repository. The apps will still be available in the git repository in the same state as they were before the GitSync configuration was deleted.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/deleteconfig.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Git repo
Once the initial commit is made, you can see the app files in the git repository. The repository will have the individual app folders and a **.meta** folder. The app folders will be named as the app name and will have the respective **JSON** file of the application. The **.meta** folder will have the `meta.json` file that contains the meta information of each application synced to git repo.
The **meta.json** file holds information about apps such as the **App name**, **last commit message**, **last commit user**, **last commit date**, **version name**, and **version id**.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/firstcommit.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Pushing changes to git repo
Once the GitSync feature is configured, you can start pushing changes to the git repository.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### App creation
When you create a new app, you will see an option to select the `Commit changes`. If you select the `commit changes` option, the changes will be committed to the git repository.
:::info
If the app name is same as the name of the existing app in the git repo, it will overwrite the existing app in the git repo.
:::
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/commitchanges.png" alt="GitLab SSH Key" />
</div>
Selecting the `Commit changes` option will create a new commit in the git repository. The commit message will be `App creation` and the author will be the user who created the app.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/author.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### App rename
Whenever an app is renamed, the changes will be automatically committed to the git repository. The commit message will be `App is renamed` and the author will be the user who renamed the app.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/apprename.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### App updates
Whenever a user makes a change in an app, they can make a commit to the git repository by clicking on the **GitSync** button on the topbar. On clicking the **GitSync** button, a modal will open with the option to enter the commit message. The user can enter the commit message and click on the **Commit changes** button to commit the changes to the git repository. Along with the commit message, the user can also see the connnected **Git repo URL** and the **last commit details**.
**Last commit details** helps the user to know the last commit message, author, date, and time. This helps the user to know the last commit details and make the commit message accordingly.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/message.png" alt="GitLab SSH Key" />
</div>
Once the changes are committed, the user can see the commit message, author, and date in the git repository.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/lastcommitmsg.png" alt="GitLab SSH Key" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### App deletion
Whenever a user deleted an app from the workspace, the app will not be deleted from the git repository. The app will be available in the git repository in the same state as it was before the app was deleted.
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### App version update
When a user creates a new version of an app, there will be an option to select the `Commit changes`. If you select the `commit changes` option, the new version of the app will be committed to the git repository.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/newversion.png" alt="GitLab SSH Key" />
</div>
The **JSON** file in the app folder will be replaced with the new version of the app, the **meta.json** file in the **.meta** folder gets updated with the new version id and version name. The commit message will be `Version creation` and the author will be the user who created the new version of the app.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/gitlab/newversion1.png" alt="GitLab SSH Key" />
</div>
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Pulling changes from git repo
You can configure the GitSync feature on another workspace to pull the changes from the git repository. To configure the GitSync feature on another workspace, follow the steps mentioned in the [Setting up GitSyncing with GitLab](#setting-up-gitsyncing-with-gitlab) section.
Once the GitSync feature is configured, go to the ToolJet dashboard and click on the three dots on the right side of the **Create new app** button. Click on the **Import from git repository** option.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/importgit.png" alt="GitLab SSH Key" />
</div>
On clicking the **Import from git repository** option, a modal will open with the dropdown to select the app to be imported from the git repository. Once the app is selected, the app name and the last commit will be displayed. Click on the **Import app** button to import the app from the git repository.
:::caution
- The app imported from the git repository cannot be edited.
- The app imported from the Git repository should have a unique name. If the app's name is the same as that of an existing app in the workspace, the user will need to either rename the existing app or delete it to successfully import another app with the same name.
- Workspace constants are not synced with the git repository. After pulling the app, if the app throws an error, the user will need to manually add the workspace constants.
:::
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/importmodal.png" alt="GitLab SSH Key" />
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Checking for updates
You can check for updates in the git repository by clicking on the **GitSync** button on the topbar. On clicking the **GitSync** button, a modal will open with the option to **Check for updates**. Click on the **Check for updates** button to check for updates in the git repository. If there are any updates, you will see the details of the updates such as commit message, author, and the date in the modal. Click on the **Pull changes** button to pull the changes from the git repository.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px', borderRadius: '6px' }} className="screenshot-full" src="/img/gitsync/updatecheck.png" alt="GitLab SSH Key" />
</div>
</div>
</div>
</TabItem>
</Tabs>

View file

@ -51,9 +51,9 @@ To protect the user's privacy, Geolocation API requests permission to locate the
- Now, go to the **Advanced** tab and enable the `Run query on page load?` option. Enabling this option will run this javascript query every time the app is opened by the user and the query will return the location
- **Save** the query and hit the fire button
- **Save** the query and hit the **Run** button
- As soon as you hit the fire button, the browser will prompt you to allow the permission to share the location access to ToolJet app. You'll need to **allow** it to return the location data
- As soon as you hit the **Run** button, the browser will prompt you to allow the permission to share the location access to ToolJet app. You'll need to **allow** it to return the location data
<div style={{textAlign: 'center'}}>

View file

@ -1,66 +1,58 @@
---
id: bulk-update-multiple-rows
title: Bulk update multiple rows in table
title: Bulk Update Multiple Rows in Table
---
# Bulk update multiple rows in table
For the purpose of this guide, it's presumed that you've already established a successful connection to your data source. We'll use PostgreSQL for this example, but you can adjust the queries based on the SQL database that you are using.
Currently, the data sources in ToolJet have operation for **bulk update(GUI mode)** but that only works for changes made in the single row. We will soon be adding a new operation for bulk updating the multiple rows but for now we can bulk update multiple rows by creating a Custom JS query.
## 1. Create a Query to Get the Data
In this guide, We have assumed that you have successfully connected the data source. For this guide, we will be using the PostgreSQL data source as an example database. Currently, this workaround can be used only for PostgreSQL and MySQL.
## 1. Create a query to get the data from the database
- Create a postgresql query in **SQL mode** and enter
- Create a PostgreSQL query in SQL mode, rename it to *users* and enter the below code.
```sql
SELECT * FROM tooljet // replace tooljet with your table name
SELECT * FROM <table name> // *replace <table name> with your table name*
```
- Enable the `Run the query on application load?` option to execute the query automatically when the application starts.
- Click on the **Run** button to fetch the data from the database.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/data.png" alt="Fetch the Data" />
</div>
## 2. Display the Data on the Table
- Drag and drop a **Table** component onto the canvas from the components library on the right.
- Click on the Table component to open its properties on the right sidebar.
- To populate the Table with the data returned by the query, add the below code under the `Data` field of the Table:
```js
{{queries.users.data}}
```
- Hit **Run** to fetch the data from the database
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/data.png)
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/populate.png" alt="Display Data on the Table" />
</div>
## 2. Display the data on the table
## 3. Make the Columns Editable
- Go to the **Components** library on the right and drag a **Table** component onto the canvas
- Click on the handle of the **Table** component to open its properties on the right sidebar
- Populate the table with the data from the query by entering **`{{queries.<queryname>.data}}`** in the **Data** field
- Under the Columns accordion, click on the column name that you want to make editable.
- On clicking the column name, a new section will open. Enable the toggle for `Make editable` to make the column editable.
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/populate.png)
</div>
## 3. Make the columns editable
- Under the **Columns** accordion, click on the column name that you want to make editable
- On clicking the column name, a new section will open. Enable the toggle for **Make editable** to make the column editable
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/editable.png)
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/editable.png" alt="Make Column Editable" />
</div>
## 4. Enable Multiple Row Selection
- Under the **Row Selection** accordion, enable the **Allow Selection**, **Highlight Selected Row**, and **Bulk Selection** option
- Under the Row Selection accordion, enable the `Allow Selection`, `Highlight Selected Row`, and `Bulk Selection` option.
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/rowselect.png)
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/rowselect.png" alt="Multiple Row Selection" />
</div>
## 5. Create a Custom JS query
- Create a new Run Javascript query and use the code below to generate the SQL query for updating multiple rows.
- Create a new Run Javascript query and use the code below to generate the SQL query for updating multiple rows. The query will be named as *runjs1* by default.
```js
const uniqueIdentifier = "id"
@ -80,54 +72,50 @@ const sql = cols.map((column) => {
return sql
```
:::info
Here the **Unique identifier** is **id**, this is the column name that is used to identify the row in the database.
Update the **Unique identifier** if you are using a different column name.
Update **table1** with the name of the table you are using.
:::
Here the unique identifier is **id** and Table component's name is **table1**. You can update the unique identifier if you are using a different column as a unique identifier. You can also update the Table name if you have renamed it, the default name is *table1*.
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/runjs1.png)
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/runjs1.png" alt="RunJS code to later the data" />
</div>
## 6. Create an Update query
## 6. Create an Update Query
- Create a postgresql query in **SQL mode** and rename it as **update**:
- Create a PostgreSQL query in SQL mode and rename it to *update*:
```sql
{{queries.runjs1.data.join(' ')}}
```
- This query will run the SQL query generated by the runjs1 query.
- This query will run the SQL query generated by the *runjs1* query.
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/update.png)
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/update.png" alt="Bulk Update Rows" />
</div>
## 7. Adding event handlers to execute queries in sequence
- Edit the **Table** component and add the event handler for **Save Changes** event so that whenever a user will edit the table and hit the **Save Changes** button the runjs1 query will run.
- Add **loading state** to table so that whenever the **users** or **update** query is running the table will show a loading state.
## 7. Adding Event Handlers to Execute Queries in Sequence
- Edit the Table component and add an event handler for `Save Changes` event so that whenever a user will edit the Table and hit the Save Changes button the *runjs1* query will run.
- Optionally, add loading state to the Table by clicking on `fx` next to the `Loading state` property.
- Use the below code to show the loading state whenever a query is getting executed.
```js
{{queries.users.isLoading || queries.update.isLoading}} // add this in the loading state field of the table
{{queries.users.isLoading || queries.update.isLoading}}
```
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/savechanges.png)
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/savechanges.png" alt="Adding Events" />
</div>
- Now, go to the **runjs1** query and add a **Event** to run update query for **Query Success** Event. This will run the update query whenever the runjs1 query will be run.
- Now, go to the *runjs1* query and add an event to run the *update* query for Query Success event. This will run the *update* query after the *runjs1* query is successfully executed.
<div style={{textAlign: 'center'}}>
![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/new/querysuccess.png)
<img style={{ border:'0' }} className="screenshot-full" src="/img/how-to/bulk-update-multiple/new/querysuccess.png" alt="Query Success" />
</div>
- Finally, go to the **update** query and add a **Event** to run the users query for **Query Success** Event. This will refresh the table whenever the update query will be run.
The data needs to reload once the *update* query runs since we want the Table component to be populated with the updated data.
- Add a new event handler in the *update* query.
- Select Query Success as the Event and Run Query as the Action.
- Select *users* as Query.
This will refresh the table whenever the *update* query will be run.

View file

@ -0,0 +1,131 @@
---
id: conditionally-format-table
title: Conditional Formatting in Table
---
Conditional formatting enhances the visual representation of data by allowing you to dynamically adjust the appearance of cells in table component based on specific conditions. This how-to guide will guide you through the process of implementing advanced conditional formatting for text color and background color in a Table component.
## Create a New Application and Set Up Data Source
1. Create a new application and add a Table component to the canvas.
2. Open the Query Panel at the bottom and click on the `+ Add` button.
3. Choose REST API as your data source and set the method to GET.
4. Enter the following URL as REST API endpoint:
```bash title="REST API Endpoint"
https://fakestoreapi.com/products
```
5. Click on the **Preview** button to view the data. Execute the query by clicking on the **Run** button.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/conditionally-format/query.png" alt="Table Component With Data" />
</div>
## Display Data on the Table
1. Hide the Query Panel and click on the Table component to open its configuration panel.
2. Under **Table Data**, enter the following code:
```js title="Table Data"
{{queries.restapi1.data}}
```
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/conditionally-format/tabledata.png" alt="Table Component With Data" />
</div>
## Enabling Conditional Formatting
1. Go to the **Columns** property of the Table component.
2. Select the column for which you want to enable conditional formatting (e.g., category).
3. If the column type is set to `Default` or `String`, you can set the conditional formatting for **text color** and **cell background color**.
**Note**: Only `cellValue` and `rowData` can be used as identifiers for conditional formatting.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/conditionally-format/column.png" alt="Table Component With Data" />
</div>
## Conditional Formatting using Cell Value
### Example 1: Changing Text Color Based on Cell Value
1. Select the `Rate` column which has a column type of `Default`/`String`. This column contains the rating of each product on a scale of 1 to 5.
2. Under the **Text Color** propert, enter the following condition:
```js
{{cellValue < 2 ? 'red' : cellValue > 2 && cellValue < 3 ? 'Orange' : 'green'}}
```
The above condition will change the text color to red if the cell value is less than 2, orange if the cell value is greater than 2 and less than 3, and green if the cell value is greater than 3.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/conditionally-format/textcv.png" alt="Table Component With Data" />
</div>
### Example 2: Changing Cell Background Color Based on Cell Value
- Select the `Rate` column, enter the following condition under the **Cell Background Color** property:
```js
{{cellValue >= 4 ? 'lightgreen' : cellValue >= 3 ? 'lightyellow' : 'lightcoral'}}
```
The above condition will change the cell background color to lightgreen if the cell value is greater than or equal to 4, lightyellow if the cell value is greater than or equal to 3, and lightcoral if the cell value is less than 3.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/conditionally-format/cellcv.png" alt="Table Component With Data" />
</div>
## Conditional Formatting using Row Data
### Example 1: Changing Text Color Based on Row Data
- Select the `Title` column, enter the following condition under the **Text Color** property:
```js
{{rowData.price > 50 ? '#D9534F' : (rowData.rating.rate >= 4 ? '#5CB85C' : rowData.rating.rate >= 3 ? '#F0AD4E' : '#D9534F' )}}
```
The above condition will change the text color of the `Title` based on the value of the `price` and `rating` columns. If the value in the `price` column is greater than 50, the text color will be red. If the value in the `rating` column is greater than or equal to 4, the text color will be green. If the value in the `rating` column is greater than or equal to 3, the text color will be yellow. Otherwise, the text color will be red.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/conditionally-format/textrd.png" alt="Table Component With Data" />
</div>
### Example 2: Changing Cell Background Color based on Row Data
- In this example, we will change the cell background color of the `Title` column based on the category of the product.
- Select the `Title` column, enter the following condition under the **Cell Background Color** property:
```js
{{rowData.category === "electronics" ? 'cyan' : rowData.category === "jewelery" ? 'pink' : 'lightgray'}}
```
The above condition will change the cell background color of the `Title` column based on the value of the `category` column. If the value in the `category` column is `electronics`, the cell background color will be cyan. If the value in the `category` column is `jewelery`, the cell background color will be pink. Otherwise, the cell background color will be lightgray.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/conditionally-format/cellrd.png" alt="Table Component With Data" />
</div>
---
By following these steps, you can implement advanced conditional formatting for text color and cell background color in your Table component. Experiment with different conditions and color combinations to create visually appealing and informative tables in your applications.

View file

@ -1,63 +1,58 @@
---
id: delete-multiple-rows
title: Delete multiple rows in table
title: Delete Multiple Rows in a Table
---
The table component in the ToolJet has the option for bulk selection of rows that can have various use cases such as **updating** or **deleting** records. However, the datasources does not support bulk delete or bulk update operations.
This guide explains how to delete multiple rows from a table, assuming you've already connected to a data source. We'll use PostgreSQL for this example, but you can adjust the queries based on the SQL database that you are using.
In this guide, we will learn how we can delete multiple rows in a table. We have assumed that you have successfully connected the data source. For this guide, we will be using the PostgreSQL data source as an example database, currently, this workaround can be used only for PostgreSQL and MySQL.
## 1. Create a Query to Fetch the Data from the Database
## 1. Create a query to fetch the data from the database
- Create a new query and name it *getRecords*.
- Select SQL mode and enter the following query:
Create a new query, name it `getRecords` and use SQL mode:
```sql
SELECT * FROM tooljet // replace tooljet with your table name
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/getRecords.png" alt="How-to: Delete multiple rows in table" />
- Enable the `Run the query on application load?` option to execute the query automatically when the application starts.
<div style={{textAlign: 'left'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/getRecords.png" alt="How-to: Delete Multiple Rows in Table" />
</div>
<br/>
Enable the **Run the query on application load?** option. This will ensure that the query is executed when the application is loaded.
## 2. Populating the Table with Data
## 2. Load the data on the table
Now, we will load the data on the table. For this, we will use the `getRecords` query that we created in the previous step. Drag the table component from the right sidebar and drop it on the canvas.
On table properties, go to the table data property and set the value to `{{queries.getRecords.data}}`. This will load the data from the `getRecords` query on the table.
Run the query and you should see the data loaded on the table.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/querydata.png" alt="How-to: Delete multiple rows in table" />
- Drag and drop a **Table** component on the canvas.
- In Table properties, go to the `Data` property and set the value to `{{queries.getRecords.data}}`.
- Now if you run the *getRecords* query, the returned data will be loaded in the Table component.
<div style={{textAlign: 'left'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/querydata.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
## 3. Enable bulk row selection on table
## 3. Enable Bulk Row Selection on Table
Now, we will enable the bulk row selection on the table. For this, go to the table properties and enable the **Bulk selection** option. Enabling this option will allow you to select multiple rows on the table. This option is disabled by default.
- Go to the Table properties and enable the `Bulk selection` option.
- Enabling this option will allow you to select multiple rows on the table.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/bulkselection.png" alt="How-to: Delete multiple rows in table" />
<img className="screenshot-full" src="/img/how-to/delete-rows/bulkselection.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
## 4. Create a custom javascript query
## 4. Create a Custom JavaScript Query
Now, we will create a custom javascript query that will **generate a SQL statement** to delete the selected rows from the table component based on a list of selected IDs, assuming the IDs are stored in the **id** column and that the name of the table component is **table1**. The actual database name should be replaced with **tooljet** as indicated in the SQL statemnent in the code below:
- Create a new Run Javascript code query. It will be named *runjs1* by default.
- Enter the following code:
```js
const uniqueIdentifier = "id";
const idsToDelete = Object.values(components.table1.selectedRows).map(dataUpdate => dataUpdate[uniqueIdentifier]);
const idsString = idsToDelete.map(id => `'${id}'`).join(', ');
@ -67,85 +62,70 @@ const SQL = `DELETE FROM tooljet WHERE ${uniqueIdentifier} IN (${idsString});`;
return SQL;
```
If you click on the **Preview** button, you should see the SQL statement generated by the query:
The above code generates a SQL query that deletes rows from the database table where the `id` field matches the selected IDs in ToolJet's Table component.
<div style={{textAlign: 'center'}}>
- Click on the **Preview** button to see the SQL statement generated by the query.
<img className="screenshot-full" src="/img/how-to/delete-rows/runjs.png" alt="How-to: Delete multiple rows in table" />
<div style={{textAlign: 'left'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/runjs.png" alt="How-to: Delete multiple rows in table" />
</div>
*If you're using a different column as the unique identifier, feel free to update the code accordingly. You can also update the Table name if you have renamed it, the default name is *table1*.*
- Select a few rows on the Table component and then Preview the SQL query generated by the *runjs1* query.
<div style={{textAlign: 'left'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/runjs1.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
Now, let's select a few rows on the table and then preview the SQL query generated by the javascript query:
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/runjs1.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
## 5. Create a new query to delete the rows
Now, we will create a new query to delete the rows from the table. Create a new query, name it `delete` and use SQL mode:
## 5. Create a New Query to Delete the Rows
- Create a new query, name it `delete`, and select SQL mode.
- Enter the following code:
```sql
{{queries.runjs1.data}} // replace runjs1 with the name of the javascript query
{{queries.runjs1.data}}
```
In this query, we are dynamically loading the SQL statement generated by the javascript query.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/delete.png" alt="How-to: Delete multiple rows in table" />
In this query, we are dynamically loading the SQL statement generated by the JavaScript query.
<div style={{textAlign: 'left'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/delete.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
## 6. Add a button to delete the selected rows
## 6. Add a Button to Delete the Selected Rows
Now, we will add a button to delete the selected rows from the table. Drag the button component from the right sidebar and drop it on the canvas. Edit its properties and set the **Button text** to **Delete**.
Add a new **Event** to the button on **On click** event to trigger the **Run Query** action and select the `runjs1` query that we created in the previously.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/button.png" alt="How-to: Delete multiple rows in table" />
- Drag and drop a **Button** component on the canvas.
- Edit its properties and set the `Button text` property to "Delete selected".
- Add a new **Event** to the button.
- Select On click as the Event, Run Query as the Action, and *runjs1* as the Query.
<div style={{textAlign: 'left', marginBottom: '15px'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/button.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
Optionally, we can add a loading state to the button whenever the `delete` or `getRecords` query is running:
- Optionally, we can add a loading state to the Button whenever the *delete* or *getRecords* query is running:
```js
{{queries.delete.isLoading || queries.getRecords.isLoading}}
```
Now, whenever you click on the button, the javascript query will generate a SQL statement to delete the selected rows from the table but to delete the rows from the database, we need to add event handler to the **runjs1** query to trigger the **delete** query whenever the `runjs1` query is **executed and successfull**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/eventrunjs.png" alt="How-to: Delete multiple rows in table" />
- Add a new **Event** to the *runjs1* query.
- Select Query Success as the Event, Run Query as the Action and *delete* as the Query.
<div style={{textAlign: 'left', marginBotton:'15px'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/eventrunjs.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
Now, whenever you click on the Button component, the *runjs1* query will run and generate a delete SQL statement with selected rows on the table. Once the *runjs1* query executes, the *delete* query will execute and delete the rows from the database.
Now, whenever you click on the button, the javascript query will generate a delete SQL statement with selected rows on the table and the `delete` query will delete the rows from the database.
Similarly, you can add an Event to the **delete** query to trigger the **getRecords** query whenever the `delete` query is executed and successful. This will ensure that the table is updated with the latest data from the database.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/eventdelete.png" alt="How-to: Delete multiple rows in table" />
- Add a new **Event** to the *delete* query.
- Select Query Success as the Event, Run Query as the Action and *getRecords* as the Query.
<div style={{textAlign: 'left', marginBottom:'15px'}}>
<img className="screenshot-full" src="/img/how-to/delete-rows/eventdelete.png" alt="How-to: Delete multiple rows in table" />
</div>
<br/>
## 7. Preview the application
The application is now ready. Click on the **Preview** button on the topbar of the app builder to preview the application.
By implementing this, we are ensuring that every time rows are deleted, the Table component will automatically refresh to display the most recent data fetched from the database.

View file

@ -1,98 +1,87 @@
---
id: import-external-libraries-using-runpy
title: Import external libraries using RunPy
title: Import External Libraries Using RunPy
---
ToolJet allows you to utilize python packages in your app by importing them using the [RunPy query](/docs/data-sources/run-py).
In this how-to guide, we will import a few packages and use it in the application.
In this how-to guide, we will import a few packages and use them in the application.
:::caution Unsupported modules
The modules that are not currently supported in Pyodide are those that have C or C++ extensions that rely on system libraries. These modules cannot be used in Pyodide because it runs in a web browser, which does not have access to the underlying system libraries that the C or C++ extensions rely on. Additionally, Pyodide uses a version of Python that has been compiled to WebAssembly, which does not support the same system calls as a regular version of Python. Therefore, any module that requires access to system libraries or system calls will not work in Pyodide.
Modules with C/C++ extensions needing system libraries won't work in Pyodide, as it runs in a web browser without system library access. Pyodide, based on WebAssembly-compiled Python, also doesn't support certain system calls.
:::
- Create a new application and then create a new RunPy query from the query panel.
<div style={{textAlign: 'center'}}>
- Start by creating a new application in ToolJet.
- From the Query Panel, add a new RunPy query - it will be named *runpy1* by default.
<div style={{textAlign: 'left', marginBotton: '15px'}}>
<img className="screenshot-full" src="/img/how-to/import-python/runpy.png" alt="Import external libraries using RunPy" />
</div>
</div>
- Use micropip to install packages like Pandas and NumPy. **Run** the query to complete installation.
- Let's write some code for importing packages. We will first import the micropip which is a package installer for Python and then we will install the `Pandas` and `NumPy` using micropip. **Run** the query to install the packages.
```python
import micropip
await micropip.install('pandas')
await micropip.install('numpy')
```
```python
import micropip
await micropip.install('pandas')
await micropip.install('numpy')
```
<div style={{textAlign: 'center'}}>
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/import-python/installing.png" alt="Import external libraries using RunPy"/>
</div>
</div>
:::tip
Enable the **Run this query on application load?** option to make the packages available throughout the application.
:::
- Enable `Run this query on application load?` to make these packages available every time the application loads.
## Examples
## Generating Random Numbers with NumPy
### Array of random numbers of using NumPy
- Create a RunPy query using NumPy's random module to generate random numbers.
- Let's create a **RunPy** query that will use **random** module from the **NumPy** package and the query will generate array of random numbers.
```python
from numpy import random
x = random.binomial(n=10, p=0.5, size=10)
print(x)
```
<div style={{textAlign: 'center'}}>
```python
from numpy import random
x = random.binomial(n=10, p=0.5, size=10)
print(x)
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/import-python/random.gif" alt="Import external libraries using RunPy"/>
</div>
</div>
*You can check the output on the browser's console.*
:::info
You can check the output on the browser's console.
:::
## Parse CSV data
### Parse CSV data
- Create a RunPy query to parse CSV data using `StringIO`, `csv`, and `Pandas` module.
- Let's create a RunPy query that will parse the data from the csv file. In this query we will use `StringIO`, `csv`, and `Pandas` module.
```python
from io import StringIO
import csv
import pandas as pd
```python
from io import StringIO
import csv
import pandas as pd
scsv = components.filepicker1.file[0].content
scsv = components.filepicker1.file[0].content
f = StringIO(scsv)
reader = csv.reader(f, delimiter=',')
f = StringIO(scsv)
reader = csv.reader(f, delimiter=',')
df = pd.DataFrame(reader)
df = pd.DataFrame(reader)
print(df.info())
print(df)
```
<div style={{textAlign: 'center'}}>
print(df.info())
print(df)
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/import-python/csvparse.png" alt="Import external libraries using RunPy"/>
</div>
</div>
- Add a file picker component on the canvas and set a event handler for **On file loaded** event to **Run Query** that we created for parsing the data.
<div style={{textAlign: 'center'}}>
- Add a **File Picker** component on the canvas
- Select `On File Loaded` as the Event and Run Query as the Action.
- Select the query we just created as the Query.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/import-python/event.png" alt="Import external libraries using RunPy"/>
</div>
</div>
- Finally, let's load a csv file on the file picker and check the output by the RunPy query on the browser console.
<div style={{textAlign: 'center'}}>
- Finally, load a csv file on the File Picker component, **Run** related RunPy query and check the output on the browser console.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/import-python/console.gif" alt="Import external libraries using RunPy"/>
</div>
</div>

View file

@ -0,0 +1,238 @@
---
id: print-multi-tabs-report
title: Print data from multiple tabs
---
In this guide, we will learn how to print data from multiple tabs in ToolJet. This will be useful when you want to print an invoice or a report from your ToolJet application. For example, a tooljet app that has a set of tabs for each invoice and you want to print all the tabs in one go.
## UI of the app
Build an app with a set of tabs for each record. Each tab will have a set of fields to display the invoice details. In the example below, we have tabs component and each tab has a set of fields to display the record details.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
**Dropdown**: For selecting a specific patient whose data user want to load in the tabs.
**Tabs**: Each tab represents different type of medical record for the selected patient. For this app, we have 5 tabs for each patient. Each tab has a id starting from 0 to 4.
**Button**: Clicking on the button will print the data from all the tabs. The button has two events, the details for which we will share later in this guide.
</div>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/appui.png" alt="Print data from multiple tabs" />
</div>
## Load data from database
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
For this app, we are using tooljet database with table name `patient_data`. We created a query called `getPatientList` to fetch data from the database.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/data.png" alt="Print data from multiple tabs" />
</div>
</div>
Once the data is successfully loaded on the tabs and the app is working as expected, we can move to the next step.
## Printing data from multiple tabs
To print data from multiple tabs, we will create few javascript queries. Using event handlers, we will run these javascript queries in a sequence to print data from all the tabs.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
Before we start creating the javascript queries, we need to add a few events to the button component:
| Event | Action | Description |
|:--- |:--- |:--- |
| `On click` | Set variable | Set a variable with key `lastSelectedTab` and value to `{{components.tabs1.currentTab}}`. This will store the id of the currently selected tab in the variable. |
| `On click` | Run query | Select the query named `viewTabs` to run when the button is clicked. |
**Note**: We will create the `viewTabs` query later in this guide, so you will need to add the event to the button after you've created the query.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/buttonevents.png" alt="Print data from multiple tabs" />
</div>
</div>
### viewTabs query
The `viewTabs` query is a javascript query that will run a loop to print data from all the tabs. The query will set a variable `tabIndex` that will store the id of the tab to print data from. he query will loop and increment the tabsIndex variable by 1, using the setVariable action, till the value is less than 5.
```js title="viewTabs"
if ((variables?.tabIndex ?? undefined) == undefined) {
await actions.setVariable("tabIndex", "0"); // set tabIndex to 0 if it is not set
} else if (parseInt(variables.tabIndex) < 5){
await actions.setVariable("tabIndex", (parseInt(variables.tabIndex) + 1).toString()); // increment tabIndex by 1
}
```
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
**This query will have 3 events:**
#### 1. Query Success:
For the first Query Success event, we will add a `Control component` action which will `Run only if` `{{parseInt(variables.tabIndex) < 5}}` is true, i.e. if the tabIndex is less than 5. This action will control the `Tabs` component to `Set current tab` to `{{variables.tabIndex}}`. This will set the current tab to the tab with id stored in the `tabIndex` variable, i.e. it will set the current tab to the tab whose id got recently stored in the tabIndex variable via the viewTabs query.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/q1.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
#### 2. Query Success:
For the second Query Success event, we will select `Run Query` action which will `Run only if` `{{parseInt(variables.tabIndex) < 5}}` is true. The query for this event handler will be `getTabsHTML`. We will also add a `debounce` of `100` milliseconds to this event handler.
**Note:** we will create the `getTabsHTML` query later in this guide, so you will need to add the event to the button after you've created the query.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/q2.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
#### 3. Query Success:
For the third Query Success event, we will select `Run Query` action which will `Run only if` `{{parseInt(variables.tabIndex) === 5}}` is true. The query for this event handler will be `printPDF`. This action will only run when the `tabIndex` is equal to 5, i.e. the last iteration of the loop and we will print the data from all the tabs in this iteration.
**Note:** we will create the `printPDF` query later in this guide, so you will need to add the event to the button after you've created the query.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/q3.png" alt="Print data from multiple tabs" />
</div>
</div>
Now that we have created the `viewTabs` query, we can go to the [Download](/docs/how-to/print-multi-tabs-report#printing-data-from-multiple-tabs) button and add the `viewTabs` query to the `On click` event handler.
### getTabsHTML query
The `getTabsHTML` is javascript query that will get the html of the current tab and store it in a variable. The query will have a variable `tabsHtml` that will store the html of all the tabs in the form of an array.
```js title="getTabsHTML"
actions.setVariable( // set tabsHtml variable
"tabsHtml",
[...(variables?.tabsHtml ?? [])].concat([ // add html of the current tab to the tabsHtml variable
((variables?.tabIndex ?? -1) > 0
? `<div style="top: ${ // add a div with height of 100vh to the html of the current tab
variables?.tabIndex ?? -1
}00vh; position: absolute;">` // this will help to print data from all the tabs in one go
: "") +
document.getElementsByClassName("widget-" + components.tabs1.id)[0] // get the html of the current tab
.innerHTML +
"</div>", // add the html of the current tab to the tabsHtml variable
])
);
```
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
**This query will have 1 event:**
#### 1. Query Success:
This event will have an action to `Run Query` named `viewTabs`. This will run the `viewTabs` query after the `getTabsHTML` query is successfully executed.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/gettabshtml.png" alt="Print data from multiple tabs" />
</div>
</div>
Now that we have created the `getTabsHTML` query, we can go to the [viewTabs](/docs/how-to/print-multi-tabs-report#2-query-success) query and add the `getTabsHTML` query to the `Query Success` event handler.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### printPDF query
The `printPDF` query is a JavaScript query that generates a printable document from the HTML content stored in the `tabsHtml` variable. This query will open a new window and write the HTML content of all the tabs. This will allow the user to download a PDF document that includes the formatted content of all the tabs.
```js title="printPDF"
var printContents = variables.tabsHtml; // get the html of all the tabs from the tabsHtml variable
var winPrint = window.open("", "", "width=900,height=650"); // Open a New Window for Printing
var styles = document.querySelectorAll('link, style');
var stylesHtml = "";
for (var i = 0; i < styles.length; i++) {
stylesHtml += styles[i].outerHTML;
} // gather styles from the current page
stylesHtml += '<style>@page { size: landscape; }</style>'; // add landscape orientation to the page
winPrint.document.write(
"<html><head>" +
stylesHtml +
"</head><body>" +
`<img src="https://img.freepik.com/free-vector/hospital-logo-design-vector-medical-cross_53876-136743.jpg" class="zoom-image-wrap" style="object-fit: contain; width: 177.86px; height: 36px; position: absolute; top: 100px;">`
); // add styles and logo to the page
for (var j = 0; j < printContents.length; j++) {
winPrint.document.write(printContents[j]);
} // add html of all the tabs to the page
winPrint.document.write("</body></html>"); // Document Finalization and Printing
winPrint.document.close();
winPrint.focus();
winPrint.print();
winPrint.close();
```
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
**This query will have 3 events:**
#### 1. Query Success:
This event will have an action to `Unset Variable` named `tabsIndex`. This will unset the `tabsIndex` variable after the `printPDF` query is successfully executed.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/unsetvar1.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
#### 2. Query Success:
This event will have an action to `Unset Variable` named `tabsHtml`. This will unset the `tabsHtml` variable after the `printPDF` query is successfully executed.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/unsetvar2.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
#### 3. Query Success:
This event will have an action to `Control component`. This will control the `Tabs` component to `Set current tab` to `{{variables.lastSelectedTab}}` after the `printPDF` query is successfully executed. This will set the current tab to the tab that was selected before the `Download` button was clicked.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/print-multitabs/controlcomp2.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
Now that we have created the `printPDF` query, we can go to the [viewTabs](/docs/how-to/print-multi-tabs-report#3-query-success) query and add the `printPDF` query to the `Query Success` event handler.
Finally, we can test the app by selecting a patient and clicking on the `Download` button. This will download a PDF document with the data from all the tabs.
</div>
</div>

View file

@ -0,0 +1,90 @@
---
id: setup-rsyslog
title: Setup log file generation (Rsyslog)
---
The log file serves as a comprehensive record of audit logs, capturing crucial information about various activities within the ToolJet. Follow the guide below to set up and utilize the log file feature effectively.
## Activation and Configuration
### 1. Environment Variable Setup
- To **activate** the log file feature, simply set the environment variable `LOG_FILE_PATH` to specify the desired path for the log file. For instance, if you want to use `rsyslog` as the log file path, set `LOG_FILE_PATH` to `rsyslog`.
```bash
LOG_FILE_PATH='rsyslog'
```
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setup-rsyslog/envfile.png" alt="Setup log file generation" />
</div>
- The log file path is relative to the home directory of the machine. For instance, if the home directory is `/home/tooljet`, the log file path will be `/home/tooljet/rsyslog`.
### 2. Server Restart
- After configuring the log file environment variable, it's essential to **restart the server** to initiate the log file generation process.
- This step ensures that the server recognizes the new configuration and begins recording audit logs.
## Log Rotation and Organization
### 3. Daily Log Rotation
- The log file is designed to rotate on a daily basis, creating a new log file each day. This configuration aids in efficient management and organization of audit data.
### 4. Log File Path Structure
- The log file path is determined by the `LOG_FILE_PATH` variable. It is crucial to understand that this path is relative to the home directory of the machine. For instance, if `LOG_FILE_PATH` is set to `rsyslog`, the resulting log file path will be structured as follows:
```bash
homepath/rsyslog/{process_id}-{date}/audit.log
```
- `{process_id}` is a placeholder for the unique process identifier.
- `{date}` represents the current date.
This structured path ensures that audit logs are organized by both process and date, simplifying traceability and analysis.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setup-rsyslog/timestamp.png" alt="Setup log file generation" />
</div>
### 5. Example Log Data
The log data captures essential details, such as user ID, organization ID, resource ID, resource type, action type, resource name, IP address, and additional metadata.
<details>
<summary>Example Log file data</summary>
```bash
{
level: 'info',
message: 'PERFORM APP_CREATE OF awdasdawdwd APP',
timestamp: '2023-11-02 17:12:40',
auditLog: {
userId: '0ad48e21-e7a2-4597-9568-c4535aedf687',
organizationId: 'cf8e132f-a68a-4c81-a0d4-3617b79e7b17',
resourceId: 'eac02f79-b8e2-495a-bffe-82633416c829',
resourceType: 'APP',
actionType: 'APP_CREATE',
resourceName: 'awdasdawdwd',
ipAddress: '::1',
metadata: {
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
tooljetVersion: '2.22.2-ee2.8.3'
}
},
label: 'APP'
}
```
</details>
### 6. Folder Creation:
The log file feature automatically creates a folder in the home path with the specified name (e.g., `rsyslog`). This folder serves as the root directory for the organized storage of audit logs.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setup-rsyslog/folder.png" alt="Setup log file generation" />
</div>

View file

@ -3,17 +3,13 @@ id: use-axios-in-runjs
title: Use Axios in RunJS
---
ToolJet allows you to utilize the three [libraries](/docs/data-sources/run-js#libraries) - **Moment.js**, **Lodash**, and **Axios**. In this guide, we will see a few examples on how to use **Axios** library using RunJS query.
ToolJet supports three libraries: **Moment.js**, **Lodash**, and **Axios**. This guide focuses on using the Axios library with RunJS queries. **[Axios](https://axios-http.com/docs/intro)** is a promise-based HTTP client for making requests to your own or external servers. It supports various request types like `GET`, `POST`, `PUT/PATCH`, and `DELETE`.
**[Axios](https://axios-http.com/docs/intro)** is a promise-based HTTP library that lets developers make requests to either their own or a third-party server to fetch data. It offers different ways of making requests such as `GET`, `POST`, `PUT/PATCH`, and `DELETE`.
## GET Requests
## Making Axios HTTP requests
We'll use **[JSONPlaceholder](https://jsonplaceholder.typicode.com/)**, a free API, to demonstrate GET and PUT requests.
In this section, you will make `GET` and `PUT` requests. You will be using a free “fake” API: **[JSONPlaceholder](https://jsonplaceholder.typicode.com/)**.
### Making a GET request
Create a RunJS query and copy the code below:
- Create a RunJS query and paste the code below:
```javascript
var url = "https://jsonplaceholder.typicode.com/users/1";
@ -23,7 +19,7 @@ var data = (await axios.get(url)).data;
return data
```
In the code snippet, a variable url is declared which is assigned the URL of the JSON API. Then another variable is declared which sends a GET request to the JSON API. Save the query and hit Preview to view the data returned by the API.
*This code sets up a URL variable, makes a GET request to the API, and returns the data. Preview the query to see the API's response.*
<div style={{textAlign: 'center'}}>
@ -31,9 +27,9 @@ In the code snippet, a variable url is declared which is assigned the URL of the
</div>
### Making a POST request
## POST Requests
A post request is a little different because you will be passing some data in the request to the server. In the request, you will be creating a user and passing in details for that user. The code snippet for the request will look something like this:
- Create a RunJS query and paste the code below:
```javascript
var url = "https://jsonplaceholder.typicode.com/users";
@ -47,7 +43,8 @@ var data = axios.post(url,{
return data
```
The Axios POST request uses an object after the request URL to define the properties you want to create for your user. Once the operation has been completed, there will be a response from the server. In the screenshot below, you can see the that it return **Status: 201** which means the request has been fulfilled and resulted in a new resource being created.
This POST request sends user details to the server. The server's response, as shown below, includes **Status: 201** indicating successful resource creation.
<div style={{textAlign: 'center'}}>
@ -55,8 +52,7 @@ The Axios POST request uses an object after the request URL to define the proper
</div>
:::tip
Check out the tutorial on **[Build GitHub star history tracker](https://blog.tooljet.com/build-github-stars-history-app-in-5-minutes-using-low-code/)** that utilizes the axios library.
:::
To see Axios in action in a project, check out this tutorial:
**[Build GitHub star history tracker](https://blog.tooljet.com/build-github-stars-history-app-in-5-minutes-using-low-code/)**.

View file

@ -3,7 +3,7 @@ id: marketplace-plugin-github
title: GitHub
---
ToolJet can connect to GitHub account to read and write data. In order for ToolJet to access and manipulate data on GitHub, a **GitHub Personal Access Toke**n is necessary to authenticate and interact with the GitHub API.
ToolJet can connect to GitHub account to read and write data. In order for ToolJet to access and manipulate data on GitHub, a **GitHub Personal Access Token** is necessary to authenticate and interact with the GitHub API.
<div style={{textAlign: 'center'}}>

View file

@ -79,9 +79,9 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -107,8 +107,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -118,8 +118,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -153,9 +153,9 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.
@ -163,4 +163,3 @@ For specific issues or questions, refer to our **[Slack](https://tooljet.slack.c

View file

@ -96,9 +96,9 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -237,9 +237,9 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -163,8 +163,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -45,8 +45,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -64,8 +64,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -90,8 +90,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -73,8 +73,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -79,8 +79,8 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -30,9 +30,9 @@ If this is a new installation of the application, you may start directly with ve
- It is **crucial to perform a comprehensive backup of your database** before starting the upgrade process to prevent data loss.
- Ensure that your current version is v2.23.3-ee2.10.2 before upgrading.
- Ensure that your current version is v2.23.0-ee2.10.2 before upgrading.
- Users on versions earlier than v2.23.3-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
- Users on versions earlier than v2.23.0-ee2.10.2 must first upgrade to this version before proceeding to v2.24.3-ee2.10.2.
For specific issues or questions, refer to our **[Slack](https://tooljet.slack.com/join/shared_invite/zt-25438diev-mJ6LIZpJevG0LXCEcL0NhQ#)**.

View file

@ -1,23 +1,24 @@
---
id: how-to-access-values
title: How to Access Values?
title: Access Values
---
In ToolJet, double curly braces `{{}}` can be used to retrieve data returned by queries, access values related to components and pass custom code. You can see the list of all accessible values in the **[Inspector](/docs/how-to/use-inspector/)** tab in the left sidebar.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px'}} className="screenshot-full" src="/img/tooljet-concepts/writing-custom-code/inspector.png" alt="Check Available Values Using Inspector" />
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Accessing Values
## Accessing Values
The **queries** keyword can be used to access data returned by queries. For example:`{{queries.getSalesData.data}}`
Similarly, the **components** keyword can be used to access data in the components and other component-related variables. For example: `{{components.table1.selectedRow.id}}`.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooljet-concepts/writing-custom-code/inspector.png" alt="Check Available Values Using Inspector" />
</div>
</div>

View file

@ -0,0 +1,27 @@
---
id: actions
title: Actions
---
In ToolJet, actions are versatile functions that can be triggered by events within an app. Based on user interaction, actions can be configured to display alerts, run queries, switch pages, and perform other tasks.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Types of Actions
ToolJet supports a variety of actions. For instance, Show alert action displays a pop-up message, Run query executes data queries you've created, and Open webpage directs to a new webpage. Some of the other actions include navigating to another ToolJet app, managing modals, copying text to the clipboard, setting values in localStorage, and generating downloadable files from application data.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooljet-concepts/actions/actions-preview.png" alt="Preview Of Actions" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Ways to Configure Actions
Actions can be triggered in response to various events, such as button presses or successful query executions. To set up actions, you can establish a **[new event](/docs/tooljet-concepts/what-are-events/)** within the configuration settings of any component or query. Alternatively, for more dynamic interactions, you can utilize a **[RunJS query](/docs/how-to/run-actions-from-runjs/)**. This approach enables action triggering based on user interactions or even at designated time intervals.
</div>
Checkout all the available actions under the **[Actions Reference](/docs/actions/show-alert)** dropdown for more information.

View file

@ -0,0 +1,12 @@
---
id: component-specific-actions
title: Component Specific Actions
---
Component Specific Actions are specialized actions that are unique to each component, meaning they can perform tasks that are specific only to that component. For instance, the **Text Input** component has its own set of specific actions like `setText` that allows us to set the value of the component. Component Specific Actions actions can be triggered in two ways: through event handlers or by executing RunJS code.
<div style={{textAlign: 'center'}}>
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/component-specific-actions/component-specific-actions-preview.png" alt="Preview Of Component Specific Actions" />
</div>
Read more about how you can utilize Component Specific Actions **[here](/docs/actions/control-component/)**.

View file

@ -1,42 +1,53 @@
---
id: what-are-components
title: What Are Components?
title: Components
---
Components in ToolJet serve as the building blocks for creating applications. They are pre-designed elements that you can drag and drop onto the canvas in the App-Builder. ToolJet comes with 45+ built-in components.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-components/drag-drop-components.gif" alt="Drag And Drop Components" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-components/drag-drop-components.gif" alt="Drag And Drop Components" />
</div>
These components range from basic UI elements like buttons, text fields, and tables, to more complex elements like kanban, charts, and maps. By using components, you can quickly assemble a functional and visually appealing application without having to write code from scratch.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Customizing Components
Components are highly customizable and interactive. Once you place a component on the canvas, you can easily modify its properties, styles, and behaviors through the configuration panel on the right side of the App-Builder. This allows you to make your application dynamic and responsive.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-components/component-config.gif" alt="Component Configuration" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-components/component-config.gif" alt="Component Configuration" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Using Components With Data
In ToolJet, components can be easily connected to various data sources like databases, APIs, and third-party services through **[queries](what-are-queries)**. Once the data is fetched, you can bind it to components like tables, charts, and more.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-components/adding-data-to-component.png" alt="Adding Data To Component" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-components/adding-data-to-component.png" alt="Adding Data To Component" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Custom Components
ToolJet allows for the creation of custom components using React. This feature is invaluable for developers who require functionalities beyond the 45+ built-in components that ToolJet offers. To create a custom component, you can drag and drop a **[Custom Component](/docs/widgets/custom-component/)** on the canvas and configure its data and code.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-components/custom-components.png" alt="Custom Components" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-components/custom-components.png" alt="Custom Components" />
</div>
By incorporating custom React components, you can significantly extend the capabilities of your ToolJet applications, allowing for a more tailored and unique user experience.
</div>
By incorporating custom React components, you can significantly extend the capabilities of your ToolJet applications, allowing for a more tailored and unique user experience.
To explore the full list of components in ToolJet, go through the **[Component Library](/docs/widgets/bounded-box)**.

View file

@ -1,12 +1,10 @@
---
id: what-are-datasources
title: What Are Data Sources?
title: Data Sources
---
Data sources are pivotal as they enable us to fetch and send data to and from different sources including databases, external APIs, or services. Once a data source is configured, it can be shared across all apps within a workspace.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Types and Management of Data Sources
@ -14,19 +12,19 @@ Data sources are pivotal as they enable us to fetch and send data to and from di
Apart from its built-in database, ToolJet supports a range of external data sources which can be broadly categorized into databases, external APIs, and services. To manage these data sources, ToolJet provides a data source manager that can be opened by clicking on the **Data Sources** button located on the left-sidebar of the App-Builder.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-datasources/data-source-manager.png" alt="Data Source Manager" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-datasources/data-source-manager.png" alt="Data Source Manager" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Adding A Data Source
## Adding a Data Source
Adding a new data source is as easy as filling out a form; users can click on the Data Sources button in the left-sidebar, navigate to the required data source, click on the corresponding **Add** button and enter the credentials.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-datasources/configure-data-source.gif" alt="Configuring Data Source" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-datasources/configure-data-source.gif" alt="Configuring Data Source" />
</div>
</div>

View file

@ -1,6 +1,6 @@
---
id: what-are-events
title: What Are Events?
title: Events
---
Events are used to run queries, show alerts and other functionalities based on triggers such as button clicks or query completion. Events can be chained together to run a series of logical operations. For example, the completion of one query could trigger another event that runs a second query, and so on. This way, a single user interaction, like clicking a button, could set off a chain of events.
@ -11,7 +11,7 @@ Events are used to run queries, show alerts and other functionalities based on t
Suppose you have a query that refreshes data when a user clicks on a button, and you also want to display a pop-up alert upon successful data refresh. In ToolJet, you can configure an event to trigger a query upon clicking the button, followed by another event to display a pop-up alert confirming the successful data refresh after the query execution is completed.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-events/events-configuration.png" alt="Event Configuration" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-events/events-configuration.png" alt="Event Configuration" />
</div>
</div>
@ -24,6 +24,8 @@ Setting up event handlers to manage such triggers and responses is a straightfor
</div>
For detailed information about the events related to components, please refer to their respective documentation.

View file

@ -0,0 +1,19 @@
---
id: exposed-variables
title: Exposed Variables
---
Exposed Variables help in accessing and manipulating data within components. These variables are automatically created and updated as users interact with the application. Whether it's capturing text from a text editor or retrieving selections from a dropdown menu, exposed variables are integral for dynamic data handling in ToolJet applications.
<div style={{textAlign: 'center'}}>
<img style={{marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/exposed-variables/exposed-variables-preview.png" alt="Preview Of Exposed Variables" />
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Accessing Exposed Variables
Each component in ToolJet has its own set of exposed variables, which hold specific data related to that component. For example, in the Text Input component, the `value` variable is used. This variable is updated every time a user enters something in the text editor. It can be dynamically accessed using JavaScript notation: `{{components.textinput1.value}}`. This feature allows developers to easily track and utilize the data entered by users in real-time.
</div>
For detailed information about the exposed variables of the components, please refer to their respective documentation.

View file

@ -0,0 +1,19 @@
---
id: inspector
title: Inspector
---
ToolJet's Inspector is a valuable feature for viewing data related to various elements of your application. This includes information about queries, components, global variables, page-related variables, user-set variables, and constants.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Sections In Inspector
The Inspector panel is divided into six main sections: **Queries, Components, Globals, Variables, Page,** and **Constants**. In the Queries section, you can check the details of your executed queries. The Components section helps you check properties related to each component in your app. Globals provide important overall information about your app, like user details and settings. The Variables section shows custom variables you've set. In the Page section, you can see properties specific to each page. Lastly, Constants hold fixed values like API keys, ensuring consistency across your application.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooljet-concepts/inspector/inspector-preview.png" alt="Preview Of The Inspector Tab" />
</div>
</div>
To learn more about the Inspector option in the sidebar, go through this **[how-to](/docs/how-to/use-inspector)** guide.

View file

@ -1,12 +1,12 @@
---
id: integrating-data
title: Integrating Data
title: Queries
---
Queries allows you to interact with various data sources, such as databases, APIs, and third-party services. They act as the bridge between your application's components and the data you wish to display, manipulate, or store.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/integrating-data/query-example.png" alt="Styles Tab" />
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/integrating-data/query-example.png" alt="Styles Tab" />
</div>
These queries are constructed in the Query Panel in the App-Builder, a dedicated section within the ToolJet App-Builder, where you can write low-code or custom SQL statements, API requests, or other data retrieval methods.
@ -15,7 +15,7 @@ These queries are constructed in the Query Panel in the App-Builder, a dedicated
You can configure queries to run automatically when an application loads, or trigger them based on specific events or user actions. For example, you could set up a query to run when a user clicks a button, fills out a form, or selects an item from a dropdown menu. This enables you to create dynamic, interactive applications.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/integrating-data/trigger-query.png" alt="Trigger Query" />
<img style={{ marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/integrating-data/trigger-query.png" alt="Trigger Query" />
</div>

View file

@ -0,0 +1,26 @@
---
id: pages
title: Pages
---
ToolJet allows you to create multi-page applications. By using the **Pages** panel on the left sidebar of the app-builder, you can create, manage and navigate through different pages of your application. The ability to create multiple pages allows for diverse functionalities within a single app.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Managing Pages
The Pages panel provides several options for managing your pages. You can add new pages by clicking the `+` button on the Pages header. Each new page can be named and customized. The Settings option lets you hide the page navigation sidebar. Additionally, the Pages panel also offers various page-specific options like renaming, marking a page as the home page, hiding or duplicating pages, and adding event handlers.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooljet-concepts/pages/pages-preview.png" alt="Preview Of Pages Panel" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Advanced Page Features
ToolJet's Pages panel also includes advanced features like Page Handle, which is the unique slug at the end of the application URL, and can be customized. Pages can be set as the default landing (home) page, or hidden from the page navigation menu. You can duplicate pages, making exact copies for different uses or even add event handlers. Furthermore, pages can be disabled or deleted, with certain restrictions like the inability to delete a home page.
</div>
To understand each functionality associated with Pages, read this **[document](/docs/tutorial/pages/)**.

View file

@ -1,11 +1,9 @@
---
id: permissions
title: Securing Applications Through Permissions
title: User Groups
---
ToolJet employs a Role-Based Access Control (RBAC) system to manage security and access to its resources, which include apps, folders, and workspace variables.
In this system, Admins have the authority to invite Users to their workspaces and assign them to specific Groups. Each Group is associated with a set of Permissions that dictate what level of access its members have to various resources.
ToolJet employs a Role-Based Access Control (RBAC) system to manage security and access to its resources, which include apps, folders, and workspace variables. In this system, Admins have the authority to invite Users to their workspaces and assign them to specific Groups. Each Group is associated with a set of Permissions that dictate what level of access its members have to various resources.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
@ -33,7 +31,6 @@ By default, there are two groups: **All Users**, which contains all workspace me
## Setting Permissions Based on Groups and Permissions
To secure your applications in ToolJet, you can leverage Groups and Permissions. For instance, you could create a custom group named Finance Team and assign it permissions to only access financial apps and variables within the workspace. When you invite new users, you can directly assign them to this group, ensuring they only have access to the resources they need to perform their tasks. You can also make the app public and make it accessible to users without the need to log in.
By thoughtfully configuring these settings, you can create a secure environment tailored to your organization's needs.
</div>
Read more about managing users and groups **[here](/docs/tutorial/manage-users-groups/)**.

View file

@ -1,6 +1,6 @@
---
id: what-are-queries
title: What Are Queries?
title: Queries
---
**Queries** act as a bridge between the application and data sources. Queries help interact with data sources like databases or APIs. They fetch or update data based on events like button clicks, making apps dynamic.
@ -13,7 +13,7 @@ title: What Are Queries?
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-queries/query-panel.png" alt="Query Panel" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-queries/query-panel.png" alt="Query Panel" />
</div>
</div>
@ -25,7 +25,7 @@ title: What Are Queries?
Queries run when triggered by app events, such as clicking a button. They can fetch new data or change existing data, and the results can be displayed in the app using tables or charts. This makes data interaction in your app straightforward and effective.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px'}} className="screenshot-full" src="/img/tooljet-concepts/what-are-queries/trigger-query.png" alt="Trigger Query Config" />
<img className="screenshot-full" src="/img/tooljet-concepts/what-are-queries/trigger-query.png" alt="Trigger Query Config" />
</div>
</div>

View file

@ -0,0 +1,31 @@
---
id: run-js
title: Run JavaScript code
---
The **Run JavaScript code** query in ToolJet enables users to write and execute custom JavaScript code within their applications. It provides a flexible way to add custom logic and interact with various components. Whether its transforming data, manipulating component properties, or performing actions based on certain conditions, Run JavaScript code significantly enhances the capabilities of your ToolJet applications.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Interacting with Components and Actions
The JavaScript code you write can interact with various components of the ToolJet application. For example, you could attach an event handler to a button so that when it's clicked, it triggers Run Javascript code to run. Additionally, you can construct the JavaScript code to manipulate the properties of other components. This allows for dynamic interactions within your application.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooljet-concepts/run-js/run-js-preview.png" alt="Preview Of Run JS" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Advanced Features and Libraries
Run JavaScript code also supports advanced functionalities such as setting and unsetting variables, showing or closing modals, copying content to the clipboard, and even logging out users. This is done by using specific syntax and functions. Moreover, you can utilize libraries like Moment, Lodash, and Axios for more complex operations.
</div>
To learn more about Run JavaScript code, go through the below list of documents:
- **[Use Axios in RunJS](/docs/how-to/use-axios-in-runjs/)**
- **[Run Actions From RunJS](/docs/how-to/run-actions-from-runjs/)**
- **[Import External Libraries Using RunJS](/docs/how-to/import-external-libraries-using-runjs/)**
- **[Access a User's Location Using RunJS](/docs/how-to/access-users-location/)**

View file

@ -3,28 +3,27 @@ id: styling-components
title: Styling Components
---
<div style={{marginLeft: "40px", marginRight: "40px"}}>
Styling components in ToolJet is a straightforward yet powerful way to enhance the visual appeal and usability of your application. Once you've dragged and dropped a component onto the canvas in the App-Builder, you can access its styling options through the configuration panel on the right side. The **Styles** tab on the configuration panel allows you to modify various visual properties such as colors, fonts, borders, and dimensions. You can also apply conditional styling based on data or user interactions, enabling you to create a more dynamic and responsive user interface.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
Styling components in ToolJet is a straightforward yet powerful way to enhance the visual appeal and usability of your application. Once you've dragged and dropped a component onto the canvas in the App-Builder, you can access its styling options through the configuration panel on the right side.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/styling-components/styles-tab.png" alt="Styles Tab" />
</div>
The Styles tab on this panel allows you to modify various visual properties such as colors, fonts, borders, and dimensions. You can also apply conditional styling based on data or user interactions, enabling you to create a more dynamic and responsive user interface.
## Intuitive Styling Options
## Styling Options
The styling options in ToolJet are designed to be intuitive, eliminating the need for extensive CSS or design experience. You can easily change the background color of a button, adjust the font size of a text field, or add padding and margins to layout components. These styling changes are immediately reflected on the canvas, providing real-time feedback as you build your application.
<div style={{textAlign: 'center'}}>
<img style={{padding: '10px', marginBottom:'15px'}} className="screenshot-full" src="/img/tooljet-concepts/styling-components/styling-options.gif" alt="Styling Options" />
<img className="screenshot-full" src="/img/tooljet-concepts/styling-components/styling-options.gif" alt="Styling Options" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Custom CSS
Beyond basic styling, ToolJet also offers advanced customization capabilities. For users who are comfortable with CSS, there's the option to add custom classes. This opens up endless possibilities for fine-tuning the appearance and behavior of your application. Whether you're aiming for a specific brand aesthetic or need to meet particular accessibility standards, ToolJet's styling options give you the flexibility to create an application that not only functions well but also looks great.
By injecting **Custom CSS**, users can easily override default styles, offering a straightforward and efficient approach to visual customization. To add Custom Styles, users can navigate to the **Custom styles** tab under Workspace Settings in the ToolJet dashboard. For instance, changing the default color of a button involves identifying the component's class and applying the desired CSS changes on the Custom Styles page. This approach ensures that all instances of the app reflect the new styling, like changing button colors, without the need to edit each button individually. This ensures consistent theming across the workspace.
</div>
Continue reading about Custom CSS **[here](/docs/app-builder/customstyles/)**.

View file

@ -0,0 +1,11 @@
---
id: super-admin
title: Super Admin
---
The Super Admin in ToolJet plays a critical role in managing the instance by having full access to all workspaces, users, and groups. Super Admins differ significantly from standard Admins, possessing a broader range of privileges. They can manage users in any workspace, including archiving or unarchiving them, and have unrestricted access to all workspaces. This allows Super Admins to create, edit, or delete apps in any user's personal workspace. They also have the authority to access and modify the ToolJet database across all workspaces, an ability not granted to regular Admins.
## Advanced Control and Customization
Beyond regular management tasks, Super Admins can implement more intricate settings like white labeling, enabling multiplayer editing, and managing instance licenses. They also have the power to restrict personal workspace creation for users, ensuring tighter control over the workspace environment. These advanced capabilities underscore the Super Admin's pivotal role in overseeing the comprehensive management and customization of the ToolJet instance.
Read more about super admins **[here](/docs/enterprise/superadmin/)**.

View file

@ -0,0 +1,26 @@
---
id: workspace-constants
title: Workspace Constants
---
Workspace Constants in ToolJet help in maintaining consistency and security across your applications. These constants are essentially predefined values like tokens, secret keys, or API keys, which remain unaltered during an application's runtime.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Environment-Specific Configurations and Security
One of the key functionalities of Workspace Constants is allowing environment-specific configurations. This is particularly useful for managing sensitive data such as API keys and database credentials securely. The Constants ensure that such critical information is effectively managed across different environments like development, staging, and production. Moreover, to enhance security, Workspace Constants are resolved server-side. This means the actual values of the constants are not sent with network payloads; instead, the server resolves these values, thereby keeping them secure from client-side exposure.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooljet-concepts/workspace-constants/workspace-constants-preview.png" alt="Workspace Constants Preview" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Access Control and Usage in Application Development
Access to creating, updating, or deleting Workspace Constants is restricted to Admins, ensuring tight control over these critical values. All users with editing permissions in the app builder and global datasource connection can utilize these constants, promoting consistent usage across various application components. The syntax for using a Workspace Constant is straightforward: `{{constants.constant_name}}`. This uniform approach simplifies the application building process, making it more efficient and secure.
</div>
For a deep-dive in workspace constants, go through **[this](/docs/org-management/workspaces/workspace_constants/)** documentation.

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