Merge pull request #5899 from ToolJet/release/v2.3.0

Release v2.3.0
This commit is contained in:
Midhun G S 2023-03-31 16:23:19 +05:30 committed by GitHub
commit c6d71908a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
651 changed files with 112673 additions and 27591 deletions

View file

@ -16,6 +16,12 @@ PG_USER=<db username>
PG_HOST=<db host>
PG_PASS=<db password>
#DATABASE CONFIG using string
# If you intent you use the string and if the connection does not support ssl. Please use the below format.
# postgres://username:password@hostname:port/database_name?sslmode=disable
# TOOLJET DATABASE
ENABLE_TOOLJET_DB=
TOOLJET_DB=

View file

@ -164,4 +164,4 @@ jobs:
- run: npm --prefix server ci
- run: npm --prefix server run db:create
- run: npm --prefix server run db:migrate
- run: NODE_OPTIONS=--max_old_space_size=16096 npm --prefix server run test:e2e -- --silent --testTimeout=10000
- run: NODE_OPTIONS=--max_old_space_size=8096 npm --prefix server run test:e2e -- --silent --testTimeout=20000

2
.nvmrc
View file

@ -1 +1 @@
v14.17.3
v18.3.0

View file

@ -1 +1 @@
2.2.2
2.3.0

View file

@ -67,7 +67,7 @@ ToolJet is an **open-source low-code framework** to build and deploy internal to
<hr>
## Quickstart
The easiest way to get started with ToolJet is by creating a [ToolJet Cloud](https://tooljet.com) account. ToolJet Cloud offers a hosted solution of ToolJet. If you want to self-host ToolJet, kindly proceed to [deployment documentation](https://docs.tooljet.com/docs/setup/architecture).
The easiest way to get started with ToolJet is by creating a [ToolJet Cloud](https://tooljet.com) account. ToolJet Cloud offers a hosted solution of ToolJet. If you want to self-host ToolJet, kindly proceed to [deployment documentation](https://docs.tooljet.com/docs/setup/).
You can deploy ToolJet on Heroku for free using the one-click-deployment button only until **28th November 2022**.
<p align="center">

View file

@ -74,6 +74,21 @@ export default class Create extends Command {
let repoUrl;
const commonHygenArgs = [
'plugin',
'new',
'--name',
`${args.plugin_name}`,
'--type',
`${type}`,
'--display_name',
`${name}`,
'--plugins_path',
`${pluginsPath}`,
];
const hygenArgs = !marketplace ? [...commonHygenArgs, '--docs_path', `${docsPath}`] : commonHygenArgs;
if (marketplace) {
const buffer = fs.readFileSync(path.join('server', 'src', 'assets', 'marketplace', 'plugins.json'), 'utf8');
const pluginsJson = JSON.parse(buffer);
@ -89,21 +104,6 @@ export default class Create extends Command {
});
}
const hygenArgs = [
'plugin',
'new',
'--name',
`${args.plugin_name}`,
'--type',
`${type}`,
'--display_name',
`${name}`,
'--plugins_path',
`${pluginsPath}`,
'--docs_path',
`${docsPath}`,
];
CliUx.ux.action.start('creating plugin');
await runner(hygenArgs, {

View file

@ -1,5 +1,6 @@
/node_modules
/cypress.env.json
/cypress/reports
/cypress/screenshots
/cypress/downloads
/cypress/videos

View file

@ -70,12 +70,7 @@ module.exports = defineConfig({
experimentalRunAllSpecs: true,
baseUrl: "http://localhost:8082",
specPattern: [
"cypress/e2e/editor/**/*.cy.js",
"cypress/e2e/exportImport/**/*.cy.js",
"cypress/e2e/authentication/**/*.cy.js",
"cypress/e2e/dashboard/multi-workspace/**/*.cy.js",
"cypress/e2e/dashboard/*.cy.js",
],
"cypress/e2e/editor/widget/*.cy.js"],
numTestsKeptInMemory: 1,
redirectionLimit: 10,
experimentalRunAllSpecs: true,

View file

@ -4,6 +4,7 @@ export const cyParamName = (paramName = "") => {
export const commonSelectors = {
toastMessage: ".go3958317564",
oldToastMessage: ".go318386747",
toastCloseButton: '[data-cy="toast-close-button"]',
editButton: "[data-cy=edit-button]",
searchField: "[data-cy=widget-search-box]",
@ -81,6 +82,7 @@ export const commonSelectors = {
invitedUserName: '[data-cy="invited-user-name"]',
invitedUserEmail: '[data-cy="invited-user-email"]',
acceptInviteButton: '[data-cy="accept-invite-button"]',
databaseIcon: `[data-cy="database-icon"]`,
profileSettings: '[data-cy="profile-settings"]',
workspaceSettingsIcon: '[data-cy="workspace-settings-icon"]',
manageUsersOption: '[data-cy="manage-users-option"]',
@ -168,6 +170,7 @@ export const commonSelectors = {
currentWorkspaceName: (workspaceName) => {
return `[data-cy="${cyParamName(workspaceName)}-current-workspace-name"]`;
},
appHeaderLable: '[data-cy="app-header-label"]',
folderListcard: (folderName) => {
return `[data-cy="${cyParamName(folderName)}-list-card"]`;
@ -271,7 +274,7 @@ export const commonWidgetSelector = {
'[data-cy="action-options-action-selection-field"]',
componentTextInput: '[data-cy="action-options-text-input-field"]',
changeLayoutButton: "[data-cy= 'change-layout-button']",
changeLayoutToMobileButton: '[data-cy="button-change-layout-to-mobile"]',
changeLayoutToMobileButton: '[data-cy="button-change-layout-to-mobile"]',
changeLayoutToDesktopButton: '[data-cy="button-change-layout-to-desktop"]',
sidebarinspector: "[data-cy='left-sidebar-inspect-button']",

View file

@ -0,0 +1,93 @@
import { cyParamName } from "./common";
export const databaseSelectors = {
addTableButton: '[data-cy="add-table-button"]',
tablePageHeader: '[data-cy="tables-page-header"]',
doNotHaveTableText: '[data-cy="do-not-have-table-text"]',
searchTableInputField: '[data-cy="search-table-input"]',
allTablesSection: '[data-cy="all-table-column"]',
allTableSubheader: '[data-cy="all-tables-subheader"]',
createNewTableHeader: '[data-cy="create-new-table-header"]',
tableNameLabel: '[data-cy="table-name-label"]',
tableNameInputField: '[data-cy="table-name-input-field"]',
addColumnsHeader: '[data-cy="add-columns-header"]',
nameLabel: '[data-cy="name-input-field-label"]',
typeLabel: '[data-cy="type-input-field-label"]',
defaultLabel: '[data-cy="default-input-field-label"]',
idInputField: '[data-cy="name-input-field-id"]',
typeInputField: '[data-cy="type-dropdown-field"]',
defaultInputField: '[data-cy="default-input-field"]',
addMoreColumnsButton: '[data-cy="add-more-columns-button"]',
deleteIcon: '[data-cy="column-delete-icon"]',
tableKebabIcon: '[data-cy="table-kebab-icon"]',
tableEditOption: '[data-cy="edit-option"]',
tableDeleteOption: '[data-cy="delete-option"]',
editTableHeader: '[data-cy="edit-table-header"]',
idColumnHeader: '[data-cy="id-column-header"]',
noRecordsText: '[data-cy="do-not-have-records-text"]',
deleteRecordButton: '[data-cy="delete-row-records-button"]',
nameInputField: (value) => {
return `[data-cy="name-input-field-${value}"]`
},
currentTable: (tableName) => {
return `[data-cy="${String(tableName).toLowerCase().replace(/\s+/g, "-")}-table"]`;
},
currentTableName: (tableName) => {
return `[data-cy="${String(tableName).toLowerCase().replace(/\s+/g, "-")}-table-name"]`;
},
columnHeader: (columnName) => {
return `[data-cy="${String(columnName).toLowerCase().replace(/\s+/g, "-")}-column-header"]`;
},
checkboxCell: (idColumn) => {
return `[data-cy="${idColumn}-checkbox-table-cell"]> div > input`
},
};
export const createNewColumnSelectors = {
addNewColumnButton: '[data-cy="add-new-column-button"]',
createNewColumnHeader: '[data-cy="create-new-column-header"]',
columnNameLabel: '[data-cy="column-name-input-field-label"]',
dataTypeLabel: '[data-cy="data-type-input-field-label"]',
defaultValueLabel: '[data-cy="default-value-input-field-label"]',
columnNameInputField: '[data-cy="column-name-input-field"]',
dataTypeDropdown: '[data-cy="data-type-dropdown-section"]',
defaultValueInputField: '[data-cy="default-value-input-field"]',
};
export const createNewRowSelectors = {
addNewRowButton: '[data-cy="add-new-row-button-text"]',
createNewRowHeader: '[data-cy="create-new-row-header"]',
idColumnNameLabel: '[data-cy="id-column-name-label"]',
serialDataTypeLabel: '[data-cy="integer-data-type-label"]',
idColumnInputField: '[data-cy="id-input-field"]',
columnNameLabel: (columnName) => {
return `[data-cy="${String(columnName).toLowerCase().replace(/\s+/g, "-")}-column-name-label"]`;
},
columnNameInputField: (columnName) => {
return `[data-cy="${String(columnName).toLowerCase().replace(/\s+/g, "-")}-input-field"]`;
},
};
export const filterSelectors = {
filterButton: '[data-cy="filter-button"]',
selectColumnField: '[data-cy="select-column-field"]',
selectOperationField: '[data-cy="select-operation-field"]',
valueInputField: '[data-cy="value-input-field"]',
deleteIcon: '[data-cy="delete-icon"]',
addConditionLink: '[data-cy="add-condition-link"]',
};
export const sortSelectors = {
sortButton: '[data-cy="sort-button"]',
selectColumnField: '[data-cy="select-column-field"]',
selectOrderField: '[data-cy="select-order-field"]',
deleteIcon: '[data-cy="delete-icon"]',
addConditionLink: '[data-cy="add-another-condition-link"]',
};

View file

@ -7,13 +7,15 @@ export const appVersionSelectors = {
},
createVersionLink: '[data-cy="create-version-link"]',
createVersionTitle: '[data-cy="create-version-title"]',
createNewVersion: '[data-cy="create-new-version-title"]',
versionNamelabel: '[data-cy="version-name-label"]',
appVersionMenuField: '[data-cy="app-version-menu-field"]',
appVersionMenuField:
'[data-cy="app-version-selector"] .custom-version-selector__indicators',
versionNameInputField: '[data-cy="version-name-input-field"]',
createVersionFromLabel: '[data-cy="create-version-from-label"]',
createVersionInputField: '[data-cy="create-version-input-field"]',
createVersionButton: '[data-cy="create-version-button"]',
appVersionContentList: '[data-cy="app-version-content"] .dropdown-item',
createVersionInputField: '[data-cy="create-version-from-input-field"]',
createNewVersionButton: '[data-cy="create-new-version-button"]',
appVersionContentList: ".custom-version-selector__menu-list",
};
export const exportAppModalSelectors = {
selectVersionTitle: '[data-cy= "select-a-version-to-export-title"]',
@ -34,6 +36,7 @@ export const exportAppModalSelectors = {
export const importSelectors = {
dropDownMenu: '[data-cy="import-dropdown-menu"]',
importAnApplication: '[data-cy="import-an-application"]',
importOptionLabel: '[data-cy="import-option-label"]',
importOptionInput: '[data-cy="import-option-input"]',
};
};

View file

@ -34,22 +34,22 @@ export const postgreSqlSelector = {
addQueriesCard: '[data-cy="postgresql-add-query-card"]',
headerQueryManager: '[data-cy="header-queries-on-query-manager"]',
labelNoQuery: '[data-cy="no-query-text"]',
createQueryButton: '[data-cy="create-query-button"]',
querySearchIcon: '[data-cy="query-search-icon"]',
createQueryButton: '[data-cy="query-create-and-run-button"]',
querySearchBar: '[data-cy="home-page-search-bar"]',
labelSelectDataSource: '[data-cy="label-select-datasource"]',
queryTabGeneral: '[data-cy="query-tab-general"]',
queryLabelInputField: '[data-cy="query-label-input-field"]',
queryLabelInputField: '[data-cy="query-rename-input"]',
queryPreviewButton: '[data-cy="query-preview-button"]',
queryCreateAndRunButton: '[data-cy="query-create-and-run-button"]',
queryCreateAndRunButton: '[data-cy="query-run-button"]',
queryCreateDropdown: '[data-cy="query-create-dropdown"]',
queryCreateAndRunOption: '[data-cy="query-create-and-run-option"]',
queryCreateOption: '[data-cy="query-create-option"]',
queryInputField: '[data-cy="query-input-field"]',
labelTransformation: '[data-cy="label-query-transformation"]',
toggleTransformation: '[data-cy="toggle-query-transformation"]',
toggleTransformation: '[data-cy="transformation-toggle-switch"]',
inputFieldTransformation: '[data-cy="transformation-input-input-field"]',
headerQueryPreview: '[data-cy="header-query-preview"]',
headerQueryPreview: '.py-2',
previewTabJson: '[data-cy="preview-tab-json"]',
previewTabRaw: '[data-cy="preview-tab-raw"]',
@ -60,11 +60,11 @@ export const postgreSqlSelector = {
queryTabAdvanced: '[data-cy="query-tab-advanced"]',
labelRunQueryOnPageLoad: '[data-cy="label-run-query-on-page-load"]',
labelRunQueryOnPageLoad: '[data-cy="run-on-app-load-toggle-label"]',
labelRequestConfirmationOnRun:
'[data-cy="label-request-confirmation-on-run"]',
labelShowNotification: '[data-cy="label-show-notification"]',
toggleNotification: '[data-cy="toggle-show-notification"]',
'[data-cy="confirmation-before-run-toggle-label"]',
labelShowNotification: '[data-cy="notification-on-success-toggle-label"]',
toggleNotification: '[data-cy="notification-on-success-toggle-switch"]',
labelSuccessMessageInput: '[data-cy="label-success-message-input"]',
notificationDurationInput: '[data-cy="label-notification-duration-input"]',
addEventHandler: '[data-cy="add-event-handler"]',

View file

@ -0,0 +1,9 @@
export const editVersionSelectors = {
editVersionTitle: '[data-cy="edit-version-title"]',
versionNameInputField: '[data-cy="edit-version-name-input-field"]',
saveButton: '[data-cy="save-button"]'
};
export const deleteVersionSelectors = {
modalMessage: '[data-cy="modal-message"]',
yesButton: '[data-cy="yes-button"]',
};

View file

@ -1,7 +1,7 @@
export const s3Text = {
awsS3: "AWS S3",
accessKey: "Access key",
secretKey: "Secret keyEncrypted",
secretKey: "Secret key",
region: "Region",
customEndpoint: "Custom Endpoint",
alertRegionIsMissing: "Region is missing",
@ -10,7 +10,7 @@ export const s3Text = {
placeholderEnterSecretKey: "Enter secret key",
labelRegion: "Region",
region: "N. california",
alertInvalidUrl: "Invalid URL:",
alertInvalidUrl: "Invalid URL",
accessKeyError:
"The AWS Access Key Id you provided does not exist in our records.",
sinatureError:

View file

@ -6,6 +6,7 @@ export const path = {
loginPath: "/login",
profilePath: "/settings",
confirmInvite: "/confirm",
database: "/database",
};
export const commonText = {
@ -13,6 +14,7 @@ export const commonText = {
email: "dev@tooljet.io",
password: "password",
loginErrorToast: "Invalid email or password",
welcomeTooljetWorkspace: "Welcome to your new ToolJet workspace",
introductionMessage:
"You can get started by creating a new application or by creating an application using a template in ToolJet Library.",
changeIconOption: "Change Icon",
@ -65,6 +67,8 @@ export const commonText = {
"You are invited to a workspace My workspace. Accept the invite to join the workspace.",
userNameInputLabel: "Name",
acceptInviteButton: "Accept invite",
createButton: "Create",
saveChangesButton: "Save changes",
emailInputLabel: "Email",
allApplicationLink: "All apps",
notificationsCardTitle: "Notifications",

View file

@ -0,0 +1,75 @@
export const databaseText = {
allTableSubheader: "All tables",
doNotHaveTableText: "You don't have any tables yet.",
tablePageHeader: "Tables",
createNewTableHeader: "Create a new table",
tableNameLabel: "Table name",
addColumnHeader: "Add columns",
nameLabel: "Name",
typeLabel: "Type",
defaultLabel: "Default",
primaryKeyLabel: "Primary Key",
addMoreColumnsButton: " Add more columns",
editTableHeader: "Edit table",
idColumnHeader: "id",
noRecordsText: "You don't have any records yet.",
deleteRecordButton: "Delete records",
tableCreatedSuccessfullyToast: (tableName) => {
return `${tableName} created successfully`;
},
tableDeletedSuccessfullyToast: (tableName) => {
return `Table "${tableName}" deleted successfully`
},
tableEditedSuccessfullyToast: (tableName) => {
return `${tableName} edited successfully`;
},
tableExistsToast: (tableName) => {
return `Table name already exists: ${tableName}`;
},
deleteRowToast: (tableName, rowNumber) => {
return `Deleted ${rowNumber} rows from table "${tableName}"`
}
};
export const createNewColumnText = {
createNewColumnHeader: "Create a new column",
columnNameLabel: 'Column name',
dataTypeLabel: 'Data type',
defaultValueLabel: 'Default value',
columnCreatedSuccessfullyToast: 'Column created successfully',
}
export const createNewRowText = {
createNewRowHeader: 'Create a new row',
serialDataTypeLabel: 'SERIAL',
rowCreatedSuccessfullyToast: 'Row created successfully',
}
export const filterText = {
filterText: ' Filter',
operation: {
equals: "equals",
greaterThan: "greater than",
greaterThanEqual: "greater than or equal",
lessThan: "less than",
lessThanEqual: "less than or equal",
notEqual: "not equal",
like: "like",
ilike: "ilike",
match: "match",
imatch: "imatch",
in: "in",
},
}
export const sortText = {
sortByText: 'Sort by',
order: {
ascending: "Ascending",
descending: "Descending"
}
}

View file

@ -2,6 +2,6 @@ export const elasticsearchText = {
elasticSearch: "Elasticsearch",
cypressElasticsearch: "cypress-elasticsearch",
errorConnectionRefused: "connect ECONNREFUSED 127.0.0.1:9200",
errorConnectionRefused: "connect ECONNREFUSED ::1:9200",
errorGetAddrInfoNotFound: "getaddrinfo ENOTFOUND elasticsearch_host",
};

View file

@ -1,9 +1,11 @@
export const appVersionText = {
createNewVersion: "Create new version",
createVersion: "Create Version",
versionNameLabel: "Version Name",
createVersionFromLabel: "Create version from",
emptyToastMessage: "The version name should not be empty",
emptyToastMessage: "Version name should not be empty",
createdToastMessage: "Version Created",
versionNameAlreadyExists: "Version name already exists."
};
export const exportAppModalText = {
@ -19,4 +21,4 @@ export const importText = {
importOption: "Import",
couldNotImportAppToastMessage: `Could not import the app: SyntaxError: Unexpected token '<27>', "<22>PNG\r\n\u001a\n\u0000\u0000"... is not valid JSON`,
appImportedToastMessage: "App imported successfully.",
};
};

View file

@ -1,7 +1,7 @@
export const firestoreText = {
firestore: "Firestore",
cypressFirestore: "cypress-firestore",
labelPrivateKey: "Private keyEncrypted",
labelPrivateKey: "Private key",
privateKey: "Private key",
placeholderPrivateKey: "Enter private key",

View file

@ -1,9 +1,9 @@
export const mySqlText = {
errorConnectionRefused: "connect ECONNREFUSED",
errorUnknownDb: "ER_BAD_DB_ERROR: Unknown database 'test_db1'",
errorUnknownDb: "ER_BAD_DB_ERROR: Unknown database 'unknowndb'",
errorAccessDeniedAdmin1:
"ER_ACCESS_DENIED_ERROR: Access denied for user 'admin1'",
errorAccessDeniedAdmin:
"ER_ACCESS_DENIED_ERROR: Access denied for user 'admin'",
"ER_ACCESS_DENIED_ERROR: Access denied for user",
cypressMySql: "cypress-mysql",
};

View file

@ -13,7 +13,8 @@ export const postgreSqlText = {
labelSSL: "SSL",
labelDbName: "Database Name",
labelUserName: "Username",
labelPassword: "PasswordEncrypted",
labelPassword: "Password",
label: "Encrypted",
sslCertificate: "SSL Certificate",
whiteListIpText:
"Please white-list our IP address if the data source is not publicly accessible",
@ -43,8 +44,8 @@ export const postgreSqlText = {
queryModeSql: "SQL mode",
queryModeGui: "GUI mode",
headerTransformations: "Transformations",
json: "Json",
headerTransformations: "Enable Transformations",
json: "JSON",
raw: "Raw",
labelOperation: "Operation",
@ -52,7 +53,7 @@ export const postgreSqlText = {
labelPrimaryKeyColumn: "Primary key column",
labelRecordsToUpdate: "Records to update",
toggleLabelRunOnPageLoad: "Run this query on page load?",
toggleLabelRunOnPageLoad: "Run this query on application load?",
toggleLabelconfirmation: "Request confirmation before running query?",
toggleLabelShowNotification: "Show notification on success?",
labelSuccessMessage: "Success Message",

View file

@ -0,0 +1,25 @@
import { cyParamName } from "Selectors/common";
export const editVersionText = {
editVersionTitle: 'Edit Version',
saveButton: 'Save',
VersionNameUpdatedToastMessage: 'Version name updated'
}
export const deleteVersionText = {
deleteModalText: (text) => {
return `Are you sure you want to delete this version - ${cyParamName(text)}`;
},
deleteToastMessage: (version) => {
return `Version - ${cyParamName(version)} Deleted`
}
}
export const releasedVersionText = {
cannotUpdateReleasedVersionToastMessage: "You cannot update a released version",
releasedToastMessage: (version) => {
return `Version ${cyParamName(version)} released`
},
releasedModalText: "You cannot make changes to a version that has already been released. Create a new version or switch to a different version if you want to make changes.",
cannotDeleteReleasedVersionToastMessage: "You cannot delete a released version"
}

View file

@ -1,167 +0,0 @@
{
"appV2": {
"id": "fdd93c2b-f161-4b3f-a0ec-2e3c6a999495",
"name": "Wolf-App-Clone",
"slug": "fdd93c2b-f161-4b3f-a0ec-2e3c6a999495",
"isPublic": false,
"isMaintenanceOn": false,
"icon": "users",
"organizationId": "6a59d335-e48f-46b9-887d-35c9c29a38fa",
"currentVersionId": null,
"userId": "96e8d058-a015-4469-bf4c-ddb34e45a4cc",
"createdAt": "2023-02-08T09:27:49.835Z",
"updatedAt": "2023-02-08T09:27:53.135Z",
"editingVersion": {
"id": "f4b60b9b-93fe-4649-b4e8-b81d2409e04f",
"name": "v1",
"definition": {
"showViewerNavigation": true,
"homePageId": "2fad12a4-73ba-4125-a5f5-5000f438788b",
"pages": {
"2fad12a4-73ba-4125-a5f5-5000f438788b": {
"components": {},
"handle": "home",
"name": "Home"
}
},
"globalSettings": {
"hideHeader": false,
"appInMaintenance": false,
"canvasMaxWidth": 1292,
"canvasMaxWidthType": "px",
"canvasMaxHeight": 2400,
"canvasBackgroundColor": "#edeff5",
"backgroundFxQuery": ""
}
},
"appId": "fdd93c2b-f161-4b3f-a0ec-2e3c6a999495",
"createdAt": "2023-02-08T09:27:49.843Z",
"updatedAt": "2023-02-08T09:27:53.517Z"
},
"dataQueries": [],
"dataSources": [
{
"id": "80b1fd47-7807-49ea-893d-4424405f0689",
"name": "restapidefault",
"kind": "restapi",
"type": "static",
"pluginId": null,
"appVersionId": "f4b60b9b-93fe-4649-b4e8-b81d2409e04f",
"organizationId": null,
"createdAt": "2023-02-08T09:27:49.835Z",
"updatedAt": "2023-02-08T09:27:49.835Z"
},
{
"id": "b10194f0-928e-4dfd-b5dc-963baf04733e",
"name": "runjsdefault",
"kind": "runjs",
"type": "static",
"pluginId": null,
"appVersionId": "f4b60b9b-93fe-4649-b4e8-b81d2409e04f",
"organizationId": null,
"createdAt": "2023-02-08T09:27:49.835Z",
"updatedAt": "2023-02-08T09:27:49.835Z"
},
{
"id": "1295d25f-8a9e-4035-a57a-e754722d2b27",
"name": "tooljetdbdefault",
"kind": "tooljetdb",
"type": "static",
"pluginId": null,
"appVersionId": "f4b60b9b-93fe-4649-b4e8-b81d2409e04f",
"organizationId": null,
"createdAt": "2023-02-08T09:27:49.835Z",
"updatedAt": "2023-02-08T09:27:49.835Z"
},
{
"id": "8141ba0d-bb35-4052-91f7-31db4c3669fa",
"name": "runpydefault",
"kind": "runpy",
"type": "static",
"pluginId": null,
"appVersionId": "f4b60b9b-93fe-4649-b4e8-b81d2409e04f",
"organizationId": null,
"createdAt": "2023-02-08T09:27:49.847Z",
"updatedAt": "2023-02-08T09:27:49.847Z"
}
],
"appVersions": [
{
"id": "f4b60b9b-93fe-4649-b4e8-b81d2409e04f",
"name": "v1",
"definition": {
"showViewerNavigation": true,
"homePageId": "2fad12a4-73ba-4125-a5f5-5000f438788b",
"pages": {
"2fad12a4-73ba-4125-a5f5-5000f438788b": {
"components": {},
"handle": "home",
"name": "Home"
}
},
"globalSettings": {
"hideHeader": false,
"appInMaintenance": false,
"canvasMaxWidth": 1292,
"canvasMaxWidthType": "px",
"canvasMaxHeight": 2400,
"canvasBackgroundColor": "#edeff5",
"backgroundFxQuery": ""
}
},
"appId": "fdd93c2b-f161-4b3f-a0ec-2e3c6a999495",
"createdAt": "2023-02-08T09:27:49.843Z",
"updatedAt": "2023-02-08T09:27:53.517Z"
}
],
"appEnvironments": [
{
"id": "720ef2a6-b90e-447b-a227-57c086a57317",
"appVersionId": "f4b60b9b-93fe-4649-b4e8-b81d2409e04f",
"name": "production",
"isDefault": true,
"createdAt": "2023-02-08T09:27:49.846Z",
"updatedAt": "2023-02-08T09:27:49.846Z"
}
],
"dataSourceOptions": [
{
"id": "9e89863a-e396-4d56-a1d4-a7853ca55b7b",
"dataSourceId": "8141ba0d-bb35-4052-91f7-31db4c3669fa",
"environmentId": "720ef2a6-b90e-447b-a227-57c086a57317",
"options": null,
"createdAt": "2023-02-08T09:27:49.850Z",
"updatedAt": "2023-02-08T09:27:49.850Z"
},
{
"id": "217e5ca0-af26-49a7-94de-6e948f211bfe",
"dataSourceId": "80b1fd47-7807-49ea-893d-4424405f0689",
"environmentId": "720ef2a6-b90e-447b-a227-57c086a57317",
"options": {},
"createdAt": "2023-02-08T09:27:49.852Z",
"updatedAt": "2023-02-08T09:27:49.852Z"
},
{
"id": "25b8514f-f6f3-4b2f-9618-29aa352ea09f",
"dataSourceId": "b10194f0-928e-4dfd-b5dc-963baf04733e",
"environmentId": "720ef2a6-b90e-447b-a227-57c086a57317",
"options": {},
"createdAt": "2023-02-08T09:27:49.856Z",
"updatedAt": "2023-02-08T09:27:49.856Z"
},
{
"id": "5ec55cf2-809b-4b57-91c2-629d05616c53",
"dataSourceId": "1295d25f-8a9e-4035-a57a-e754722d2b27",
"environmentId": "720ef2a6-b90e-447b-a227-57c086a57317",
"options": {},
"createdAt": "2023-02-08T09:27:49.858Z",
"updatedAt": "2023-02-08T09:27:49.858Z"
}
],
"schemaDetails": {
"multiPages": true,
"multiEnv": true
}
},
"tooljetVersion": "2.1.0"
}

View file

@ -0,0 +1,73 @@
import { filterSelectors, sortSelectors } from "Selectors/database";
import { filterText, sortText } from "Texts/database";
import { navigateToDatabase } from "Support/utils/common";
import {
verifyAllElementsOfPage, createTableAndVerifyToastMessage, editTableNameAndVerifyToastMessage,
deleteTableAndVerifyToastMessage,
createNewColumnAndVerify,
navigateToTable,
addNewRowAndVerify,
filterOperation,
sortOperation,
deleteCondition,
deleteRowAndVerify
} from "Support/utils/database";
import { fake } from "Fixtures/fake";
import { randomNumber } from "Support/utils/commonWidget";
import { randomString } from "Support/utils/textInput";
describe("Database Functionality", () => {
const data = {};
data.tableName = fake.tableName;
data.newTableName = fake.tableName;
data.editTableName = fake.tableName;
data.maximumLength = randomNumber(8, 10);
data.dataType = ["varchar", "int", "float", "boolean"];
const columnDetails = () => {
let column = {
name: fake.firstName,
defaultValueDoublePrecision: Math.floor(Math.random() * (1000 - 100) + 100) / 100,
defaultValueInt: randomNumber(10, 99),
defaultValueVarchar: randomString(data.maximumLength)
};
return column;
};
let column1 = columnDetails();
let column2 = columnDetails();
let column3 = columnDetails();
let column4 = columnDetails();
let rowData = {
varcharData: randomString(data.maximumLength),
doublePrecisionData: Math.floor(Math.random() * (1000 - 100) + 100) / 100,
intData: randomNumber(10, 99),
};
beforeEach(() => {
cy.appUILogin();
});
it("Verify that all elements of the table page", () => {
navigateToDatabase();
verifyAllElementsOfPage();
createTableAndVerifyToastMessage(data.tableName, false);
createTableAndVerifyToastMessage(data.newTableName, true, [column1.name, column2.name], [data.dataType[0], data.dataType[1]], true, [column1.defaultValueVarchar, column1.defaultValueInt]);
});
it("Verify all operations of table", () => {
navigateToDatabase();
navigateToTable(data.tableName);
editTableNameAndVerifyToastMessage(data.newTableName, data.editTableName);
deleteTableAndVerifyToastMessage(data.editTableName);
createNewColumnAndVerify(data.tableName, column1.name, data.dataType[0], true, column1.defaultValueVarchar);
addNewRowAndVerify(data.tableName, false)
addNewRowAndVerify(data.tableName, false, [column1.name], true, [rowData.varcharData])
createNewColumnAndVerify(data.tableName, column2.name, data.dataType[1], false);
addNewRowAndVerify(data.tableName, false, [column1.name, column2.name], [rowData.varcharData, rowData.intData]);
addNewRowAndVerify(data.tableName, true, [column1.name, column2.name], [rowData.varcharData, rowData.intData]);
filterOperation(data.tableName, ["id"], [filterText.operation.greaterThan], ["2"]);
deleteCondition(filterSelectors.filterButton, ["id"], filterSelectors.deleteIcon)
sortOperation(data.tableName, ["id"], [sortText.order.descending])
deleteCondition(sortSelectors.sortButton, ["id"], sortSelectors.deleteIcon)
deleteRowAndVerify(data.tableName, ["1", "2"]);
});
});

View file

@ -0,0 +1,90 @@
import { appVersionSelectors } from "Selectors/exportImport";
import { editVersionSelectors } from "Selectors/version";
import { editVersionText, releasedVersionText, deleteVersionText } from "Texts/version";
import { createNewVersion } from "Support/utils/exportImport";
import { navigateToCreateNewVersionModal, verifyElementsOfCreateNewVersionModal, navigateToEditVersionModal, editVersionAndVerify, deleteVersionAndVerify, releasedVersionAndVerify, verifyDuplicateVersion, verifyVersionAfterPreview } from "Support/utils/version";
import { fake } from "Fixtures/fake";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
import { verifyModal, closeModal, navigateToAppEditor } from "Support/utils/common";
import { buttonText } from "Texts/button";
import { verifyComponent, deleteComponentAndVerify } from "Support/utils/basicComponents";
describe("App Export Functionality", () => {
var data = {};
data.appName = `${fake.companyName}-App`;
let currentVersion = "";
let newVersion = [];
let versionFrom = "";
beforeEach(() => {
cy.appUILogin();
});
it("Verify the elements of the version module", () => {
cy.createApp();
cy.get(appVersionSelectors.appVersionLabel).should("be.visible");
cy.renameApp(data.appName);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"have.value",
data.appName
);
cy.waitForAutoSave();
navigateToCreateNewVersionModal(currentVersion = "v1");
verifyElementsOfCreateNewVersionModal(currentVersion = ["v1"]);
navigateToEditVersionModal(currentVersion = "v1")
verifyModal(
editVersionText.editVersionTitle,
editVersionText.saveButton,
editVersionSelectors.versionNameInputField
);
closeModal(commonText.closeButton);
});
it("Verify all functionality for the app version", () => {
navigateToAppEditor(data.appName);
cy.get('[data-cy="widget-list-box-table"]').should("be.visible");
cy.get(".driver-close-btn").click();
cy.dragAndDropWidget("Toggle Switch", 50, 50);
verifyComponent("toggleswitch1");
navigateToCreateNewVersionModal(currentVersion = "v1");
createNewVersion((newVersion = ["v2"]), versionFrom = "v1");
verifyComponent("toggleswitch1");
deleteComponentAndVerify("toggleswitch1");
cy.dragAndDropWidget(buttonText.defaultWidgetText);
verifyComponent("button1");
navigateToCreateNewVersionModal(currentVersion = "v2");
createNewVersion((newVersion = ["v3"]), versionFrom = "v2");
verifyComponent("button1");
navigateToCreateNewVersionModal(currentVersion = "v3");
createNewVersion((newVersion = ["v4"]), versionFrom = "v1");
verifyComponent("toggleswitch1");
editVersionAndVerify(currentVersion = "v4", newVersion = ["v5"], editVersionText.VersionNameUpdatedToastMessage)
navigateToCreateNewVersionModal(currentVersion = "v5");
verifyDuplicateVersion((newVersion = ["v5"]), versionFrom = "v5")
closeModal(commonText.closeButton);
deleteVersionAndVerify(currentVersion = "v5", deleteVersionText.deleteToastMessage(currentVersion = "v5"));
cy.reload();
releasedVersionAndVerify(currentVersion = "v3")
editVersionAndVerify(currentVersion = "v3", newVersion = ["v5"], releasedVersionText.cannotUpdateReleasedVersionToastMessage);
closeModal(commonText.closeButton);
cy.reload();
closeModal(commonText.closeButton);
deleteVersionAndVerify(currentVersion = "v3", releasedVersionText.cannotDeleteReleasedVersionToastMessage)
navigateToCreateNewVersionModal(currentVersion = "v3");
createNewVersion((newVersion = ["v6"]), versionFrom = "v3");
verifyVersionAfterPreview(currentVersion = "v6")
cy.go('back');
})
});

View file

@ -104,7 +104,7 @@ describe("Data source BigQuery", () => {
fillDataSourceTextField(
firestoreText.privateKey,
bigqueryText.placehlderPrivateKey,
JSON.stringify(Cypress.env("bigquery_pvt_key")),
`${JSON.stringify(Cypress.env("bigquery_pvt_key"))}`,
"contain",
{ parseSpecialCharSequences: false, delay: 0 }
);
@ -120,9 +120,11 @@ describe("Data source BigQuery", () => {
);
cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click();
cy.get("#radix-2").click();
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", bigqueryText.cypressBigQuery)
.find("button")
.invoke("show")
.should("be.visible");
});
});

View file

@ -47,17 +47,14 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
cy.get(postgreSqlSelector.dataSourceSearchInputField).type(
'ClickHouse'
);
cy.get("[data-cy*='data-source-']")
.eq(0)
.should("contain", 'ClickHouse');
cy.get(postgreSqlSelector.dataSourceSearchInputField).type("ClickHouse");
cy.get("[data-cy*='data-source-']").eq(0).should("contain", "ClickHouse");
cy.get('[data-cy="data-source-clickhouse"]').click();
cy.get(postgreSqlSelector.dataSourceNameInputField).should( //username,password,host,port,protocol,dbname,usepost, trimquery,gzip,debug,raw
cy.get(postgreSqlSelector.dataSourceNameInputField).should(
//username,password,host,port,protocol,dbname,usepost, trimquery,gzip,debug,raw
"have.value",
'ClickHouse'
"ClickHouse"
);
cy.get(postgreSqlSelector.labelUserName).verifyVisibleElement(
"have.text",
@ -65,7 +62,7 @@ describe("Data sources", () => {
);
cy.get(postgreSqlSelector.labelPassword).verifyVisibleElement(
"have.text",
'Password'
"Password"
);
cy.get(postgreSqlSelector.labelHost).verifyVisibleElement(
"have.text",
@ -82,36 +79,31 @@ describe("Data sources", () => {
);
cy.get('[data-cy="label-protocol"]').verifyVisibleElement(
"have.text",
'Protocol'
"Protocol"
);
cy.get('[data-cy="label-use-post"]').verifyVisibleElement(
"have.text",
'Use Post'
"Use Post"
);
cy.get('[data-cy="label-trim-query"]').verifyVisibleElement(
"have.text",
'Trim Query'
"Trim Query"
);
cy.get('[data-cy="label-use-gzip"]').verifyVisibleElement(
"have.text",
'Use Gzip'
"Use Gzip"
);
cy.get('[data-cy="label-debug"]').verifyVisibleElement(
"have.text",
'Debug'
);
cy.get('[data-cy="label-raw"]').verifyVisibleElement(
"have.text",
'Raw'
);
cy.get(postgreSqlSelector.labelIpWhitelist).scrollIntoView().verifyVisibleElement(
"have.text",
postgreSqlText.whiteListIpText
);
cy.get(postgreSqlSelector.buttonCopyIp).scrollIntoView().verifyVisibleElement(
"have.text",
postgreSqlText.textCopy
"Debug"
);
cy.get('[data-cy="label-raw"]').verifyVisibleElement("have.text", "Raw");
cy.get(postgreSqlSelector.labelIpWhitelist)
.scrollIntoView()
.verifyVisibleElement("have.text", postgreSqlText.whiteListIpText);
cy.get(postgreSqlSelector.buttonCopyIp)
.scrollIntoView()
.verifyVisibleElement("have.text", postgreSqlText.textCopy);
cy.get(postgreSqlSelector.linkReadDocumentation).verifyVisibleElement(
"have.text",
@ -123,52 +115,49 @@ describe("Data sources", () => {
postgreSqlText.buttonTextTestConnection
)
.click();
cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement(
"have.text",
postgreSqlText.couldNotConnect
);
cy.get(postgreSqlSelector.connectionFailedText)
.scrollIntoView()
.verifyVisibleElement("have.text", postgreSqlText.couldNotConnect);
cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement(
"have.text",
postgreSqlText.buttonTextSave
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).scrollIntoView().verifyVisibleElement(
"have.text",
'getaddrinfo ENOTFOUND undefined'
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL, { timeout: 60000 })
.scrollIntoView()
.verifyVisibleElement("have.text", "getaddrinfo ENOTFOUND undefined");
});
it("Should verify the functionality of PostgreSQL connection form.", () => {
selectDataSource('ClickHouse');
selectDataSource("ClickHouse");
cy.clearAndType(
'[data-cy="data-source-name-input-filed"]',
postgreSqlText.psqlName
"cypress-clickhouse"
);
fillDataSourceTextField(
postgreSqlText.labelHost,
postgreSqlText.placeholderEnterHost,
"localhost",
Cypress.env("pg_host")
);
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"5432"
);
fillDataSourceTextField(postgreSqlText.labelPort, "8123", "8123");
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"postgres"
"database name",
"{del}"
);
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"postgres"
Cypress.env("clickhouse_user")
);
cy.get(postgreSqlSelector.passwordTextField).type(
Cypress.env("pg_password")
Cypress.env("clickhouse_password")
);
cy.get(".css-1e1a1lx-control > .css-s59k37-ValueContainer")
.click()
.type(`HTTP{enter}`);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
@ -183,8 +172,9 @@ describe("Data sources", () => {
cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click();
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", postgreSqlText.psqlName)
.should("have.text", "cypress-clickhouse")
.find("button")
.invoke("show")
.should("be.visible");
});
});

View file

@ -47,27 +47,20 @@ describe("Data sources", () => {
postgreSqlText.allCloudStorage
);
cy.get(postgreSqlSelector.dataSourceSearchInputField).type(
'CosmosDB'
);
cy.get("[data-cy*='data-source-']")
.eq(0)
.should("contain", 'CosmosDB');
cy.get(postgreSqlSelector.dataSourceSearchInputField).type("CosmosDB");
cy.get("[data-cy*='data-source-']").eq(0).should("contain", "CosmosDB");
cy.get('[data-cy="data-source-cosmosdb"]').click();
cy.get(postgreSqlSelector.dataSourceNameInputField).should(
"have.value",
'CosmosDB'
"CosmosDB"
);
cy.get('[data-cy="label-end-point"]').verifyVisibleElement(
"have.text",
'End point'
"End point"
);
cy.get('[data-cy="label-key"]').verifyVisibleElement(
"have.text",
'Key'
);
cy.get('[data-cy="label-key"]').verifyVisibleElement("have.text", "Key");
cy.get(postgreSqlSelector.labelIpWhitelist).verifyVisibleElement(
"have.text",
postgreSqlText.whiteListIpText
@ -97,41 +90,27 @@ describe("Data sources", () => {
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement(
"have.text",
'Invalid URL: '
"Invalid URL"
);
});
it("Should verify the functionality of PostgreSQL connection form.", () => {
selectDataSource('CosmosDB');
it("Should verify the functionality of CosmosDB connection form.", () => {
selectDataSource("CosmosDB");
cy.clearAndType(
'[data-cy="data-source-name-input-filed"]',
'cypress-cosmosdb'
"cypress-cosmosdb"
);
fillDataSourceTextField(
postgreSqlText.labelHost,
postgreSqlText.placeholderEnterHost,
Cypress.env("pg_host")
"End point",
'https://your-account.documents.azure.com',
Cypress.env("cosmosdb_end_point")
);
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"5432"
);
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"postgres"
);
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"postgres"
);
cy.get(postgreSqlSelector.passwordTextField).type(
Cypress.env("pg_password")
'Key',
'Enter your key',
Cypress.env("cosmosdb_key")
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
@ -147,8 +126,9 @@ describe("Data sources", () => {
cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click();
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", 'cypress-cosmosdb')
.should("have.text", "cypress-cosmosdb")
.find("button")
.invoke('show')
.should("be.visible");
});
});

View file

@ -113,7 +113,7 @@ describe("Data sources", () => {
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement(
"have.text",
'Invalid URL: undefined://:5984/_all_dbs'
'Invalid URL'
);
});
@ -127,27 +127,28 @@ describe("Data sources", () => {
fillDataSourceTextField(
postgreSqlText.labelHost,
postgreSqlText.placeholderEnterHost,
Cypress.env("pg_host")
'',
Cypress.env("couchdb_host")
);
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"5432"
'5984 ',
"5984"
);
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"postgres"
'database name',
'{del}'
);
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"postgres"
'username for couchDB',
Cypress.env("couchdb_user")
);
cy.get('.css-1e1a1lx-control > .css-s59k37-ValueContainer').type('HTTP{enter}')
cy.get(postgreSqlSelector.passwordTextField).type(
Cypress.env("pg_password")
Cypress.env("couchdb_password"), {log:false}
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
@ -165,6 +166,7 @@ describe("Data sources", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", postgreSqlText.psqlName)
.find("button")
.invoke("show")
.should("be.visible");
});
});

View file

@ -127,7 +127,7 @@ describe("Data source Elasticsearch", () => {
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"443"
"9200"
);
fillDataSourceTextField(
@ -153,7 +153,7 @@ describe("Data source Elasticsearch", () => {
"elasticsearch_user"
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
verifyCouldnotConnectWithAlert("Response Error");
verifyCouldnotConnectWithAlert("write EPROTO C062440602000000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:355:");
fillDataSourceTextField(
postgreSqlText.labelUserName,
@ -164,10 +164,11 @@ describe("Data source Elasticsearch", () => {
.clear()
.type("elasticsearch_password");
cy.get(postgreSqlSelector.buttonTestConnection).click();
verifyCouldnotConnectWithAlert("Response Error");
verifyCouldnotConnectWithAlert("write EPROTO C062440602000000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:355:");
cy.get(postgreSqlSelector.passwordTextField)
.clear()
.type(Cypress.env("elasticsearch_password"));
cy.get('.form-check-input').click()
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {

View file

@ -103,7 +103,7 @@ describe("Data source Firestore", () => {
fillDataSourceTextField(
firestoreText.privateKey,
firestoreText.placeholderPrivateKey,
JSON.stringify(Cypress.env("firestore_pvt_key")),
`${JSON.stringify(Cypress.env("firestore_pvt_key"))}`,
"contain",
{ parseSpecialCharSequences: false, delay: 0 }
);
@ -122,6 +122,7 @@ describe("Data source Firestore", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("contain.text", firestoreText.cypressFirestore)
.find("button")
.invoke("show")
.should("be.visible");
});
});

View file

@ -104,7 +104,7 @@ describe("Data sources", () => {
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement(
"have.text",
'Invalid URL: undefined://:8086/influxdb/cloud/api//ping'
'Invalid URL'
);
});
@ -118,28 +118,15 @@ describe("Data sources", () => {
fillDataSourceTextField(
postgreSqlText.labelHost,
postgreSqlText.placeholderEnterHost,
Cypress.env("pg_host")
'',
Cypress.env("influxdb_host")
);
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"5432"
);
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"postgres"
);
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"postgres"
);
cy.get(postgreSqlSelector.passwordTextField).type(
Cypress.env("pg_password")
'8086 ',
"8086"
);
cy.get('.css-1e1a1lx-control > .css-s59k37-ValueContainer').click().type('HTTP{enter}')
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
@ -156,6 +143,7 @@ describe("Data sources", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", postgreSqlText.psqlName)
.find("button")
.invoke('show')
.should("be.visible");
});
});

View file

@ -145,8 +145,8 @@ describe("Data sources", () => {
"5432"
);
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"Database",
"Enter name of the database",
"postgres"
);
fillDataSourceTextField(

View file

@ -6,7 +6,15 @@ import {
fillDataSourceTextField,
selectDataSource,
} from "Support/utils/postgreSql";
import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource";
import { connectMongo, openMongoQueryEditor,selectQueryType } from "Support/utils/mongoDB";
import {
verifyCouldnotConnectWithAlert,
resizeQueryPanel,
query,
verifypreview,
addInput,
} from "Support/utils/dataSource";
describe("Data source MongoDB", () => {
beforeEach(() => {
@ -93,18 +101,18 @@ describe("Data source MongoDB", () => {
postgreSqlText.buttonTextTestConnection
)
.click();
cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement(
"have.text",
postgreSqlText.couldNotConnect,
{ timeout: 65000 }
);
cy.get(postgreSqlSelector.connectionFailedText, {
timeout: 70000,
}).verifyVisibleElement("have.text", postgreSqlText.couldNotConnect, {
timeout: 65000,
});
cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement(
"have.text",
postgreSqlText.buttonTextSave
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement(
"have.text",
mongoDbText.errorConnectionRefused
"connect ECONNREFUSED ::1:27017"
);
cy.get('[data-cy="query-select-dropdown"]').type(
mongoDbText.optionConnectUsingConnectionString
@ -132,10 +140,14 @@ describe("Data source MongoDB", () => {
postgreSqlText.buttonTextTestConnection
)
.click();
cy.get(postgreSqlSelector.connectionFailedText).verifyVisibleElement(
cy.get(postgreSqlSelector.connectionFailedText, {
timeout: 70000,
}).verifyVisibleElement("have.text", postgreSqlText.couldNotConnect, {
timeout: 95000,
});
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement(
"have.text",
postgreSqlText.couldNotConnect,
{ timeout: 60000 }
'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"'
);
verifyCouldnotConnectWithAlert(mongoDbText.errorInvalisScheme);
cy.get(postgreSqlSelector.buttonSave).verifyVisibleElement(
@ -178,6 +190,169 @@ describe("Data source MongoDB", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", mongoDbText.cypressMongoDb)
.find("button")
.invoke("show")
.should("be.visible");
});
it.only("Should verify the queries of MongoDB.", () => {
connectMongo();
openMongoQueryEditor();
resizeQueryPanel();
selectQueryType('Delete Many')
addInput('collection', 'test')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
selectQueryType('List Collections')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','[{"name":"test"') //'root:[] 0 items'
selectQueryType('Insert One')
addInput('collection', 'test')
addInput('document', '{name:"mike"}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','{"acknowledged":true,"insertedId"')
selectQueryType('Find One')
addInput('collection', 'test')
addInput('filter', '{name:"mike"}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','"name":"mike"}')
selectQueryType('Find many')
addInput('collection', 'test')
addInput('filter', '{name:"mike"}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','"name":"mike"}')
selectQueryType('Total Count')
addInput('collection', 'test')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','{"count":')
selectQueryType('Count')
addInput('collection', 'test')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','{"count":');
selectQueryType('Distinct')
addInput('collection', 'test')
addInput('field', 'name')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','["mike"]');
selectQueryType('Insert Many')
addInput('collection', 'test')
addInput('documents', '[{_id:331, name:"Nina"},{_id:441, name:"mina"}, {_id:4441, name:"Steph"}, {_id:41, name:"Mark"},{_id:3131, name:"Lina"}]')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
addInput('documents', '[{_id:3113, name:"Nina"},{_id:414, name:"mina"}]')
query('preview')
verifypreview('raw','{"acknowledged":true,"insertedCount":2,"insertedIds":{"0":3113,"1":414}}');
selectQueryType('Update One')
addInput('collection', 'test')
addInput('filter', '{name:"mina"}')
addInput('update', '{$set:{name: "mike2023"}}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','{"acknowledged":true,"modifiedCount":1,"upsertedId":null,"upsertedCount":0');
selectQueryType('Update Many')
addInput('collection', 'test')
addInput('filter', '{name:"Nina"}')
addInput('update', '{$set:{name: "mike22222"}}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
addInput('filter', '{name:"mike22222"}')
addInput('update', '{$set:{name: "Mark"}}')
query('preview')
verifypreview('raw','{"acknowledged":true,"modifiedCount":2,"upsertedId":null,"upsertedCount":0');
selectQueryType('Replace One')
addInput('collection', 'test')
addInput('filter', '{name:"mike"}')
addInput('replacement', '{name: "mike2023"}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
addInput('filter', '{name:"mike"}')
addInput('replacement', '{name: "Nina"}')
query('preview')
verifypreview('raw','{"acknowledged":true,"modifiedCount":1,"upsertedId":null,"upsertedCount":0');
selectQueryType('Find One and Update')
addInput('collection', 'test')
addInput('filter', '{name:"mike"}')
addInput('update', '{$set:{name: "mike2023"}}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
addInput('filter', '{name:"Mark"}')
addInput('update', '{$set:{name: "Nina"}}')
query('preview')
verifypreview('raw','{"lastErrorObject":{"n":1,"updatedExisting":true},"value":{"_id":');
selectQueryType('Find One and Replace')
addInput('collection', 'test')
addInput('filter', '{name:"mike"}')
addInput('replacement', '{name: "mike2023"}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
addInput('filter', '{name:"mike2023"}')
addInput('replacement', '{name: "Nina"}')
query('preview')
verifypreview('raw','{"lastErrorObject":{"n":1,"updatedExisting":true},"value":{"_id":');
selectQueryType('Find One and Delete')
addInput('collection', 'test')
addInput('filter', '{name:"Nina"}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
addInput('filter', '{name:"mike2023"}')
query('preview')
verifypreview('raw','{"lastErrorObject":{"n":1},"value":{"_id":');
selectQueryType('Delete One')
addInput('collection', 'test')
addInput('filter', '{name:"mike"}')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
addInput('filter', '{name:"Lina"}')
query('preview')
verifypreview('raw','{"acknowledged":true,"deletedCount":1}');
selectQueryType('Aggregate')
addInput('collection', 'test')
addInput('pipeline', '[{$match:{name:"mike2023"}}, {$match:{_id:414}}]')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','[{"_id":414,"name":"mike2023"}]');
selectQueryType('Operations')
addInput('collection', 'test')
addInput('operations', '[{insertOne:{name:"midhun"}}]')
query('run')
cy.verifyToastMessage('.go2072408551','Query (mongodb1) completed.')
query('preview')
verifypreview('raw','{"ok":1,"writeErrors":[],"writeConcernErrors":[],"insertedIds":[{"index":');
});
});

View file

@ -2,11 +2,19 @@ import { postgreSqlSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
import { mySqlText } from "Texts/mysql";
import { commonSelectors } from "Selectors/common";
import { commonWidgetText } from "Texts/common";
import {
fillDataSourceTextField,
selectDataSource,
addQuery,
fillConnectionForm,
openQueryEditor,
selectQueryMode,
addGuiQuery,
addWidgetsToAddUser,
} from "Support/utils/postgreSql";
import { verifyCouldnotConnectWithAlert } from "Support/utils/dataSource";
import { realHover } from "cypress-real-events/commands/realHover";
describe("Data sources MySql", () => {
beforeEach(() => {
cy.appUILogin();
@ -116,17 +124,17 @@ describe("Data sources MySql", () => {
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"3306"
"3318"
);
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"test_db1"
"unknowndb"
);
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"admin"
Cypress.env("mysql_user")
);
cy.get(postgreSqlSelector.passwordTextField).type(
@ -134,11 +142,11 @@ describe("Data sources MySql", () => {
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
verifyCouldnotConnectWithAlert(mySqlText.errorUnknownDb);
verifyCouldnotConnectWithAlert("");
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"test_db"
"testdb"
);
fillDataSourceTextField(
postgreSqlText.labelUserName,
@ -146,19 +154,20 @@ describe("Data sources MySql", () => {
"admin1"
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
verifyCouldnotConnectWithAlert(mySqlText.errorAccessDeniedAdmin1);
verifyCouldnotConnectWithAlert('ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client');
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"admin"
test/spec-updation-needed-to-fix-specs-on-github-actions
Cypress.env("mysql_user")
);
cy.get(postgreSqlSelector.passwordTextField).type("testpassword");
cy.get(postgreSqlSelector.buttonTestConnection).click();
verifyCouldnotConnectWithAlert(mySqlText.errorAccessDeniedAdmin);
verifyCouldnotConnectWithAlert("ER_ACCESS_DENIED_ERROR: Access denied for user 'root'@'103.171.99.42' (using password: YES)");
cy.get(postgreSqlSelector.passwordTextField).type(
`{selectAll}{backspace}${Cypress.env("mysql_password")}`
`{selectAll}{backspace}${Cypress.env("mysql_password")}`, {log:false}
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
@ -176,6 +185,287 @@ describe("Data sources MySql", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", mySqlText.cypressMySql)
.find("button")
.invoke('show')
.should("be.visible");
});
it.only("Should verify elements of the Query section.", () => {
cy.viewport(1200, 1300)
selectDataSource("MySQL");
fillConnectionForm({
Host: Cypress.env("mysql_host"),
Port: Cypress.env("mysql_port"),
"Database Name": "testdb",
Username: Cypress.env("mysql_user"),
Password: Cypress.env("mysql_password"),
});
cy.get('[class="query-pane"]').invoke("css", "height", "calc(85%)");
openQueryEditor("MySQL");
// cy.get('[class="query-pane"]').invoke("css", "height", "calc(95%)");
// cy.get(postgreSqlSelector.addQueriesCard)
// .verifyVisibleElement("contain", mySqlText.cypressMySql)
// .click();
// cy.get(postgreSqlSelector.queryTabGeneral).verifyVisibleElement(
// "contain",
// postgreSqlText.tabGeneral
// );
// cy.get(postgreSqlSelector.queryLabelInputField).verifyVisibleElement(
// "have.value",
// postgreSqlText.firstQueryName
// );
// cy.get(postgreSqlSelector.queryPreviewButton).verifyVisibleElement(
// "have.text",
// postgreSqlText.buttonLabelPreview
// );
// cy.get(postgreSqlSelector.queryCreateAndRunButton).verifyVisibleElement(
// "have.text",
// postgreSqlText.buttonLabelCreateAndRun
// );
// cy.get(postgreSqlSelector.queryCreateDropdown).should("be.visible").click();
// cy.get(postgreSqlSelector.queryCreateAndRunOption).verifyVisibleElement(
// "have.text",
// postgreSqlText.buttonLabelCreateAndRun
// );
// cy.get(postgreSqlSelector.queryCreateOption)
// .verifyVisibleElement("have.text", postgreSqlText.buttonLabelCreate)
// .click();
// cy.get(postgreSqlSelector.queryCreateAndRunButton).verifyVisibleElement(
// "have.text",
// postgreSqlText.buttonLabelCreate
// );
cy.get('[class="query-pane"]').invoke("css", "height", "calc(85%)");
cy.get(`${postgreSqlSelector.querySelectDropdown}:eq(0)`)
.scrollIntoView()
.should("be.visible")
.click()
cy.contains("[id*=react-select-]", postgreSqlText.queryModeSql).should(
"have.text",
postgreSqlText.queryModeSql
);
cy.contains("[id*=react-select-]", postgreSqlText.queryModeGui).should(
"have.text",
postgreSqlText.queryModeGui
);
cy.get(postgreSqlSelector.queryCreateAndRunButton)
.should("be.visible")
.click();
// cy.get('[data-cy="list-query-mysql1"]').should("be.visible").click();
cy.get(postgreSqlSelector.labelTransformation)
.scrollIntoView()
.verifyVisibleElement("have.text", postgreSqlText.headerTransformations);
cy.wait(200)
cy.get(postgreSqlSelector.toggleTransformation).parent().click();
cy.get(postgreSqlSelector.inputFieldTransformation).should("be.visible");
cy.get(postgreSqlSelector.toggleTransformation).parent().click();
cy.get(postgreSqlSelector.headerQueryPreview).verifyVisibleElement(
"have.text",
postgreSqlText.buttonLabelPreview
);
cy.get(postgreSqlSelector.previewTabJson).verifyVisibleElement(
"have.text",
postgreSqlText.json
);
cy.get(postgreSqlSelector.previewTabRaw).verifyVisibleElement(
"have.text",
postgreSqlText.raw
);
selectQueryMode(postgreSqlText.queryModeGui, "4");
cy.get(postgreSqlSelector.operationsDropDownLabel).verifyVisibleElement(
"have.text",
postgreSqlText.labelOperation
);
cy.get(`${postgreSqlSelector.querySelectDropdown}:eq(1)`).click();
cy.contains('[id*="react-select-10"]', postgreSqlText.guiOptionBulkUpdate)
.should("have.text", postgreSqlText.guiOptionBulkUpdate)
.click();
cy.get(postgreSqlSelector.labelTableNameInputField).verifyVisibleElement(
"have.text",
postgreSqlText.labelTable
);
cy.get(postgreSqlSelector.labelPrimaryKeyColoumn).verifyVisibleElement(
"have.text",
postgreSqlText.labelPrimaryKeyColumn
);
cy.get('[data-cy="label-records"]').verifyVisibleElement(
"have.text",
'Records'
);
// cy.get(postgreSqlSelector.queryTabAdvanced)
// .verifyVisibleElement("contain", postgreSqlText.tabAdvanced)
// .click();
cy.get(postgreSqlSelector.labelRunQueryOnPageLoad).verifyVisibleElement(
"have.text",
postgreSqlText.toggleLabelRunOnPageLoad
);
cy.get(
postgreSqlSelector.labelRequestConfirmationOnRun
).verifyVisibleElement("have.text", postgreSqlText.toggleLabelconfirmation);
cy.get(postgreSqlSelector.labelShowNotification).verifyVisibleElement(
"have.text",
postgreSqlText.toggleLabelShowNotification
);
cy.get(postgreSqlSelector.toggleNotification).parent().click();
cy.get(postgreSqlSelector.labelSuccessMessageInput).verifyVisibleElement(
"have.text",
postgreSqlText.labelSuccessMessage
);
cy.get(postgreSqlSelector.notificationDurationInput).verifyVisibleElement(
"have.text",
postgreSqlText.labelNotificatioDuration
);
cy.get(postgreSqlSelector.addEventHandler).verifyVisibleElement(
"have.text",
commonWidgetText.addEventHandlerLink
);
cy.get(postgreSqlSelector.noEventHandlerMessage).verifyVisibleElement(
"have.text",
postgreSqlText.labelNoEventhandler
);
cy.get('[data-cy="list-query-mysql1"]').verifyVisibleElement('have.text', 'mysql1');
cy.get('[class="row query-row query-row-selected"]').realHover().then(()=>{cy.get('[data-cy="delete-query-mysql1"]').click()})
cy.get(postgreSqlSelector.deleteModalMessage).verifyVisibleElement(
"have.text",
postgreSqlText.dialogueTextDelete
);
cy.get(postgreSqlSelector.deleteModalCancelButton).verifyVisibleElement(
"have.text",
postgreSqlText.cancel
);
cy.get(postgreSqlSelector.deleteModalConfirmButton)
.verifyVisibleElement("have.text", postgreSqlText.yes)
.click();
});
it("Should verify CRUD operations on SQL Query.", () => {
let dbName ='7mmplik'
selectDataSource('MySQL');
cy.clearAndType(
postgreSqlSelector.dataSourceNameInputField,
mySqlText.cypressMySql
);
cy.get('[class="query-pane"]').invoke("css", "height", "calc(85%)");
cy.intercept("GET", "api/data_sources?**").as("datasource");
fillConnectionForm({
Host: Cypress.env("mysql_host"),
Port: Cypress.env("mysql_port"),
"Database Name": "testdb",
Username: Cypress.env("mysql_user"),
Password: Cypress.env("mysql_password"),
});
cy.wait("@datasource");
addQuery(
"table_creation",
`CREATE TABLE ${dbName} (id MEDIUMINT NOT NULL AUTO_INCREMENT, name CHAR(30) NOT NULL,email VARCHAR(255),PRIMARY KEY (id));`,
mySqlText.cypressMySql
);
addQuery(
"table_preview",
`SELECT * FROM ${dbName}`,
mySqlText.cypressMySql
);
addQuery(
"existance_of_table",
`SHOW TABLES LIKE '${dbName}';`,
mySqlText.cypressMySql
);
cy.get(postgreSqlSelector.queryPreviewButton, { timeout: 3000 }).click();
cy.get('[class="tab-pane active"]', { timeout: 3000 }).should("be.visible");
cy.get(postgreSqlSelector.previewTabRaw, { timeout: 3000 })
.scrollIntoView()
.should("be.visible", { timeout: 3000 })
.click();
cy.get('.p-3').should(
"have.text",
`[{"Tables_in_testdb (${dbName})":"${dbName}"}]`
);
// addQuery(
// "add_data_using_widgets",
// `INSERT INTO "public"."cypress_test_users"("name", "email") VALUES('{{components.textinput1.value{rightArrow}{rightArrow}', '{{}{{}components.textinput2.value{rightArrow}{rightArrow}') RETURNING "id", "name", "email";`,
// mySqlText.cypressMySql
// );
addQuery(
"truncate_table",
`TRUNCATE TABLE ${dbName}`,
mySqlText.cypressMySql
);
cy.get(postgreSqlSelector.queryPreviewButton).click();
cy.get('[class="tab-pane active"]', { timeout: 3000 }).should("be.visible");
cy.get(postgreSqlSelector.previewTabRaw).click();
cy.get('[class="tab-pane active"]').should("have.text", `{"fieldCount":0,"affectedRows":0,"insertId":0,"serverStatus":2,"warningCount":0,"message":"","protocol41":true,"changedRows":0}`);
addQuery(
"drop_table",
`DROP TABLE ${dbName}`,
mySqlText.cypressMySql
);
cy.get('[data-cy="list-query-existance_of_table"]').click();
cy.get(postgreSqlSelector.queryPreviewButton).click();
cy.get('[class="tab-pane active"]', { timeout: 3000 }).should("be.visible");
cy.get(postgreSqlSelector.previewTabRaw).click();
cy.get('[class="tab-pane active"]').should(
"have.text",
'[]'
);
// addWidgetsToAddUser();
});
it("Should verify bulk update", () => {
selectDataSource('MySQL');
cy.clearAndType(
postgreSqlSelector.dataSourceNameInputField,
mySqlText.cypressMySql
);
fillConnectionForm({
Host: Cypress.env("mysql_host"),
Port: "3318",
"Database Name": "testdb",
Username: Cypress.env("mysql_user"),
Password: Cypress.env("mysql_password"),
});
openQueryEditor(mySqlText.cypressMySql);
cy.get('[class="query-pane"]').invoke("css", "height", "calc(85%)");
selectQueryMode(postgreSqlText.queryModeGui,);
addGuiQuery("name", "email");
cy.get(postgreSqlSelector.queryCreateAndRunButton).click();
});
});

View file

@ -171,31 +171,35 @@ describe("Data sources", () => {
.should("be.visible");
});
it("Should verify elements of the Query section.", () => {
it.only("Should verify elements of the Query section.", () => {
selectDataSource(postgreSqlText.postgreSQL);
fillConnectionForm({
Host: Cypress.env("pg_host"),
Port: "5432",
"Database Name": "postgres",
Username: Cypress.env("pg_user"),
Password: Cypress.env("pg_password"),
});
fillConnectionForm(
{
Host: Cypress.env("pg_host"),
Port: "5432",
"Database Name": "postgres",
Username: Cypress.env("pg_user"),
Password: Cypress.env("pg_password"),
},
".form-switch"
);
openQueryEditor(postgreSqlText.postgreSQL);
cy.get(postgreSqlSelector.headerQueryManager).verifyVisibleElement(
"have.text",
postgreSqlText.headerQueries
);
cy.get(postgreSqlSelector.labelNoQuery).verifyVisibleElement(
"have.text",
postgreSqlText.noQueryText
);
// cy.get(postgreSqlSelector.headerQueryManager).verifyVisibleElement(
// "have.text",
// postgreSqlText.headerQueries
// ); removed
// cy.get(postgreSqlSelector.labelNoQuery).verifyVisibleElement(
// "have.text",
// postgreSqlText.noQueryText
// );
cy.get(postgreSqlSelector.createQueryButton).verifyVisibleElement(
"have.text",
postgreSqlText.buttonLabelCreateQuery
);
cy.get(postgreSqlSelector.querySearchIcon).should("be.visible");
cy.get(postgreSqlSelector.querySearchBar).should("be.visible");
cy.get('[data-cy="button-add-new-queries"]').click();
cy.get(postgreSqlSelector.labelSelectDataSource).verifyVisibleElement(
"have.text",

View file

@ -166,7 +166,7 @@ describe("Data source Redis", () => {
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"redis"
"{del}"
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
@ -183,6 +183,7 @@ describe("Data source Redis", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", redisText.cypressRedis)
.find("button")
.invoke('show')
.should("be.visible");
});
});

View file

@ -109,7 +109,7 @@ describe("Data sources", () => {
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement(
"have.text",
'Could not connect to localhost:28015.\nconnect ECONNREFUSED 127.0.0.1:28015'
'Could not connect to localhost:28015.connect ECONNREFUSED ::1:28015'
);
});

View file

@ -64,6 +64,7 @@ describe("Data sources AWS S3", () => {
);
cy.get(s3Selector.customEndpointLabel)
.verifyVisibleElement("have.text", s3Text.customEndpoint)
.parent()
.next()
.find("input")
.click();
@ -118,12 +119,14 @@ describe("Data sources AWS S3", () => {
);
cy.get(s3Selector.regionLabel)
.parent()
.next()
.find("input")
.type(`${s3Text.region}{enter}`);
cy.get(s3Selector.customEndpointLabel)
.verifyVisibleElement("have.text", s3Text.customEndpoint)
.parent()
.next()
.find("input")
.click();
@ -132,6 +135,7 @@ describe("Data sources AWS S3", () => {
verifyCouldnotConnectWithAlert(s3Text.alertInvalidUrl);
cy.get(s3Selector.customEndpointLabel)
.verifyVisibleElement("have.text", s3Text.customEndpoint)
.parent()
.next()
.find("input")
.click();
@ -171,6 +175,7 @@ describe("Data sources AWS S3", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", s3Text.cypressAwsS3)
.find("button")
.invoke('show')
.should("be.visible");
});
});

View file

@ -138,40 +138,40 @@ describe("Data sources", () => {
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"snowflake"
Cypress.env("snowflake_user")
);
fillDataSourceTextField(
"Account",
"Enter account",
Cypress.env("pg_host")
Cypress.env("snowflake_account")
);
fillDataSourceTextField(
"Password",
"Enter password",
"password"
Cypress.env("snowflake_password")
);
fillDataSourceTextField(
"Database",
"Enter database",
"snowflake"
Cypress.env("snowflake_database")
);
fillDataSourceTextField(
"Schema",
"Enter schema",
"schema"
"{del}"
);
fillDataSourceTextField(
"Warehouse",
"Enter warehouse",
"warehouse"
"{del}"
);
fillDataSourceTextField(
"Role",
"Enter role",
"role"
"{del}"
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
@ -189,6 +189,7 @@ describe("Data sources", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", "cypress-snowflake")
.find("button")
.invoke('show')
.should("be.visible");
});
});

View file

@ -132,31 +132,31 @@ describe("Data sources", () => {
fillDataSourceTextField(
postgreSqlText.labelHost,
postgreSqlText.placeholderEnterHost,
Cypress.env("pg_host")
Cypress.env("sqlserver_host")
);
fillDataSourceTextField(
"Instance",
"Enter the name of the database instance",
"5432"
Cypress.env("sqlserver_instance")
);
fillDataSourceTextField(
postgreSqlText.labelPort,
postgreSqlText.placeholderEnterPort,
"5432"
"1433"
);
fillDataSourceTextField(
postgreSqlText.labelDbName,
postgreSqlText.placeholderNameOfDB,
"postgres"
Cypress.env("sqlserver_db")
);
fillDataSourceTextField(
postgreSqlText.labelUserName,
postgreSqlText.placeholderEnterUserName,
"postgres"
Cypress.env("sqlserver_user")
);
cy.get(postgreSqlSelector.passwordTextField).type(
'Cypress.env("pg_password")'
Cypress.env("sqlserver_password")
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
@ -174,6 +174,7 @@ describe("Data sources", () => {
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", "cypress-sqlserver")
.find("button")
.invoke("show")
.should("be.visible");
});
});

View file

@ -108,7 +108,7 @@ describe("Data sources", () => {
);
});
it("Should verify the functionality of PostgreSQL connection form.", () => {
it.skip("Should verify the functionality of PostgreSQL connection form.", () => {
selectDataSource("TypeSense");
cy.clearAndType(

View file

@ -21,6 +21,7 @@ import {
selectFromSidebarDropdown,
dataPdfAssertionHelper,
dataCsvAssertionHelper,
addFilter,
} from "Support/utils/table";
import {
openAccordion,
@ -802,5 +803,117 @@ describe("Table", () => {
.and("contain", dataCsvAssertionHelper(tableText.defaultInput)[2]);
});
it("Should verify the table filter options", () => {
cy.get(
commonWidgetSelector.draggableWidget(tableText.defaultWidgetName)
).should("be.visible");
cy.get(tableSelector.filterButton).click();
addFilter(
[{ column: "name", operation: "contains", value: "Sarah" }],
true
);
verifyTableElements([{ id: 1, name: "Sarah", email: "sarah@example.com" }]);
addFilter([
{ column: "name", operation: "does not contains", value: "Sarah" },
]);
verifyTableElements([
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 3, name: "Sam", email: "sam@example.com" },
{ id: 4, name: "Jon", email: "jon@example.com" },
]);
addFilter([
{ column: "email", operation: "matches", value: "jon@example.com" },
]);
verifyTableElements([{ id: 4, name: "Jon", email: "jon@example.com" }]);
addFilter([
{
column: "email",
operation: "does not match",
value: "jon@example.com",
},
]);
verifyTableElements([
{ id: 1, name: "Sarah", email: "sarah@example.com" },
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 3, name: "Sam", email: "sam@example.com" },
]);
addFilter([{ column: "id", operation: "equals", value: "3" }]);
verifyTableElements([{ id: 3, name: "Sam", email: "sam@example.com" }]);
addFilter([{ column: "id", operation: "does not equal", value: "3" }]);
verifyTableElements([
{ id: 1, name: "Sarah", email: "sarah@example.com" },
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 4, name: "Jon", email: "jon@example.com" },
]);
addFilter([{ column: "id", operation: "greater than", value: "1" }]);
verifyTableElements([
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 3, name: "Sam", email: "sam@example.com" },
{ id: 4, name: "Jon", email: "jon@example.com" },
]);
addFilter([{ column: "id", operation: "less than", value: "3" }]);
verifyTableElements([
{ id: 1, name: "Sarah", email: "sarah@example.com" },
{ id: 2, name: "Lisa", email: "lisa@example.com" },
]);
addFilter([
{ column: "id", operation: "greater than or equals", value: "1" },
]);
verifyTableElements([
{ id: 1, name: "Sarah", email: "sarah@example.com" },
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 3, name: "Sam", email: "sam@example.com" },
{ id: 4, name: "Jon", email: "jon@example.com" },
]);
addFilter([{ column: "id", operation: "less than or equals", value: "3" }]);
verifyTableElements([
{ id: 1, name: "Sarah", email: "sarah@example.com" },
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 3, name: "Sam", email: "sam@example.com" },
]);
addFilter(
[
{ column: "id", operation: "greater than or equals", value: "2" },
{ column: "email", operation: "contains", value: "Sa" },
],
true
);
verifyTableElements([
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 3, name: "Sam", email: "sam@example.com" },
]);
addFilter(
[
{ column: "id", operation: "greater than or equals", value: "1" },
{ column: "email", operation: "does not contains", value: "Sa" },
],
true
);
verifyTableElements([{ id: 4, name: "Jon", email: "jon@example.com" }]);
addFilter([{ column: "id", operation: "is empty" }], true);
cy.notVisible('[data-cy*="-cell-"]');
addFilter([{ column: "id", operation: "is not empty" }], true);
verifyTableElements([
{ id: 1, name: "Sarah", email: "sarah@example.com" },
{ id: 2, name: "Lisa", email: "lisa@example.com" },
{ id: 3, name: "Sam", email: "sam@example.com" },
{ id: 4, name: "Jon", email: "jon@example.com" },
]);
});
it("should verify table preview", () => {});
});

View file

@ -43,7 +43,8 @@ describe("App Export Functionality", () => {
.invoke("text")
.then(() => {
cy.get(commonSelectors.editorPageLogo).should("be.visible").click();
cy.get(commonSelectors.folderPageTitle).should("be.visible");
cy.get(commonSelectors.appHeaderLable).should("be.visible");
cy.reload();
selectAppCardOption(
data.appName1,
commonSelectors.appCardOptions(commonText.exportAppOption)
@ -53,7 +54,7 @@ describe("App Export Functionality", () => {
});
it("Verify 'Export app' functionality of an application", () => {
cy.get(commonSelectors.folderPageTitle).should("be.visible");
cy.get(commonSelectors.appHeaderLable).should("be.visible");
selectAppCardOption(
data.appName1,
@ -85,6 +86,8 @@ describe("App Export Functionality", () => {
cy.exec("cd ./cypress/downloads/ && rm -rf *");
navigateToAppEditor(data.appName1);
cy.get('[data-cy="widget-list-box-table"]').should("be.visible");
cy.get(".driver-close-btn").click();
cy.get(appVersionSelectors.appVersionMenuField)
.should("be.visible")
.click();
@ -94,7 +97,7 @@ describe("App Export Functionality", () => {
.invoke("text")
.then(() => {
cy.get(commonSelectors.editorPageLogo).click();
cy.get(commonSelectors.folderPageTitle).should("be.visible");
cy.get(commonSelectors.appHeaderLable).should("be.visible");
selectAppCardOption(
data.appName1,
commonSelectors.appCardOptions(commonText.exportAppOption)
@ -128,4 +131,4 @@ describe("App Export Functionality", () => {
);
cy.exec("cd ./cypress/downloads/ && rm -rf *");
});
});
});

View file

@ -37,7 +37,7 @@ describe("App Import Functionality", () => {
});
it("Verify the Import functionality of an Application", () => {
cy.get("body").then(($title) => {
if ($title.text().includes(commonText.introductionMessage)) {
if ($title.text().includes(commonText.welcomeTooljetWorkspace)) {
cy.get(dashboardSelector.importAppButton).click();
} else {
cy.get(importSelectors.dropDownMenu).should("be.visible").click();
@ -51,15 +51,16 @@ describe("App Import Functionality", () => {
force: true,
});
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonSelectors.oldToastMessage,
importText.couldNotImportAppToastMessage
);
cy.get(importSelectors.importOptionInput).selectFile(appFile, {
force: true,
});
cy.get(".driver-close-btn").click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonSelectors.oldToastMessage,
importText.appImportedToastMessage
);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
@ -75,7 +76,8 @@ describe("App Import Functionality", () => {
);
cy.waitForAutoSave();
cy.get(commonSelectors.editorPageLogo).should("be.visible").click();
cy.get(commonSelectors.folderPageTitle).should("be.visible");
cy.get(commonSelectors.appHeaderLable).should("be.visible");
cy.reload();
selectAppCardOption(
data.appName,
commonSelectors.appCardOptions(commonText.exportAppOption)
@ -102,7 +104,7 @@ describe("App Import Functionality", () => {
force: true,
});
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonSelectors.oldToastMessage,
importText.appImportedToastMessage
);
cy.get(
@ -113,19 +115,21 @@ describe("App Import Functionality", () => {
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"have.value",
exportedAppData.name
exportedAppData.appV2.name
);
cy.get(
appVersionSelectors.currentVersionField((currentVersion = "v1"))
).verifyVisibleElement(
"have.text",
exportedAppData.appVersions[0].name
exportedAppData.appV2.appVersions[0].name
);
});
cy.exec("cd ./cypress/downloads/ && rm -rf *");
});
cy.renameApp(data.appReName);
cy.get(commonSelectors.editorPageLogo).click();
cy.get(commonSelectors.appHeaderLable).should("be.visible");
cy.reload();
navigateToAppEditor(data.appReName);
cy.get(appVersionSelectors.appVersionMenuField)
@ -141,7 +145,8 @@ describe("App Import Functionality", () => {
.then((versionText) => {
cy.log(versionText);
cy.get(commonSelectors.editorPageLogo).click();
cy.get(commonSelectors.folderPageTitle).should("be.visible");
cy.get(commonSelectors.appHeaderLable).should("be.visible");
cy.reload();
selectAppCardOption(
data.appReName,
commonSelectors.appCardOptions(commonText.exportAppOption)
@ -169,7 +174,7 @@ describe("App Import Functionality", () => {
}
);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonSelectors.oldToastMessage,
importText.appImportedToastMessage
);
cy.get(appVersionSelectors.appVersionMenuField).click();
@ -185,7 +190,7 @@ describe("App Import Functionality", () => {
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"have.value",
exportedAppData.name
exportedAppData.appV2.name
);
cy.get(
appVersionSelectors.currentVersionField(
@ -193,7 +198,7 @@ describe("App Import Functionality", () => {
)
).verifyVisibleElement(
"have.text",
exportedAppData.appVersions[1].name
exportedAppData.appV2.appVersions[1].name
);
});
});
@ -201,4 +206,4 @@ describe("App Import Functionality", () => {
});
});
});
});
});

View file

@ -65,6 +65,10 @@ function randomRgbaHex() {
return rgba;
}
function tableName() {
return faker.name.firstName();
}
Object.defineProperty(fake, "email", { get: email });
Object.defineProperty(fake, "password", { get: password });
Object.defineProperty(fake, "firstName", { get: firstName });
@ -77,3 +81,5 @@ Object.defineProperty(fake, "randomRgba", { get: randomRgba });
Object.defineProperty(fake, "randomRgb", { get: randomRgb });
Object.defineProperty(fake, "boxShadowParam", { get: boxShadowParam });
Object.defineProperty(fake, "randomRgbaHex", { get: randomRgbaHex });
Object.defineProperty(fake, "tableName", { get: tableName });

View file

@ -25,7 +25,7 @@ Cypress.Commands.add("forceClickOnCanvas", () => {
});
Cypress.Commands.add("verifyToastMessage", (selector, message) => {
cy.get(selector).eq(0).should("be.visible").and("have.text", message);
cy.get(selector).eq(0).should("be.visible").and("contain.text", message);
cy.get("body").then(($body) => {
if ($body.find(commonSelectors.toastCloseButton).length > 0) {
cy.closeToastMessage();

View file

@ -15,7 +15,7 @@
// Import commands.js using ES2015 syntax:
import './commands'
import "cypress-real-events";
// Alternatively you can use CommonJS syntax:
// require('./commands')
Cypress.on('uncaught:exception', (err, runnable) =>

View file

@ -40,7 +40,7 @@ export const randomDateOrTime = (format = "DD/MM/YYYY") => {
let startDate = new Date(2018, 0, 1);
startDate = new Date(
startDate.getTime() +
Math.random() * (endDate.getTime() - startDate.getTime())
Math.random() * (endDate.getTime() - startDate.getTime())
);
return moment(startDate).format(format);
};
@ -170,6 +170,10 @@ export const selectAppCardOption = (appName, appCardOption) => {
cy.get(appCardOption).should("be.visible").click();
};
export const navigateToDatabase = () => {
cy.get(commonSelectors.databaseIcon).click();
cy.url().should("include", path.database);
};
export const randomValue = () => {
return Math.floor(Math.random() * (1000 - 100) + 100) / 100;
};

View file

@ -7,8 +7,25 @@ export const verifyCouldnotConnectWithAlert = (dangerText) => {
}).verifyVisibleElement("have.text", postgreSqlText.couldNotConnect, {
timeout: 5000,
});
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL).verifyVisibleElement(
"contain.text",
dangerText
);
cy.get(postgreSqlSelector.dangerAlertNotSupportSSL)
.should("be.visible")
.contains(dangerText);
};
export const resizeQueryPanel=(height='90')=>{
cy.get('[class="query-pane"]').invoke("css", "height", `calc(${height}%)`);
}
export const query=(operation)=>{
cy.get(`[data-cy="query-${operation}-button"]`).click()
}
export const verifypreview=(type,data)=>{
cy.get(`[data-cy="preview-tab-${type}"]`).click()
cy.get(`[data-cy="preview-${type}-data-container"]`).verifyVisibleElement('contain.text', data)
}
export const addInput=(field,data)=>{
cy.get(`[data-cy="${field.toLowerCase()}-input-field"]`).clearAndTypeOnCodeMirror(data)
}

View file

@ -0,0 +1,313 @@
import { databaseSelectors, createNewColumnSelectors, createNewRowSelectors, filterSelectors, sortSelectors } from "Selectors/database";
import { databaseText, createNewColumnText, createNewRowText, filterText, sortText } from "Texts/database";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
export const verifyAllElementsOfPage = () => {
cy.get(databaseSelectors.addTableButton).should("be.visible");
cy.get(databaseSelectors.tablePageHeader).verifyVisibleElement(
"have.text",
databaseText.tablePageHeader
);
cy.get(databaseSelectors.doNotHaveTableText).verifyVisibleElement(
"have.text",
databaseText.doNotHaveTableText
);
cy.get(databaseSelectors.searchTableInputField).should("be.visible");
cy.get(databaseSelectors.allTablesSection).should("be.visible");
cy.get(databaseSelectors.allTableSubheader).should("be.visible");
};
export const navigateToTable = (tableName) => {
cy.get(databaseSelectors.currentTable(tableName)).scrollIntoView().should("be.visible")
cy.get(databaseSelectors.currentTableName(tableName)).verifyVisibleElement("have.text", tableName).click();
};
export const createTableAndVerifyToastMessage = (tableName, columnDetails = true, columnName = [], columnDataType = [], defaultValue, columnDefaultValue = []) => {
cy.get(databaseSelectors.addTableButton).click();
verifyAllElementsOfAddTableSection();
cy.clearAndType(databaseSelectors.tableNameInputField, tableName);
if (columnDetails) {
for (let i = 0; i < columnName.length; i++) {
cy.get(databaseSelectors.addMoreColumnsButton).click();
addNewColumnAndVerify(columnName[i], columnDataType[i], defaultValue, columnDefaultValue[i])
}
}
cy.get(commonSelectors.buttonSelector(commonText.createButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
databaseText.tableCreatedSuccessfullyToast(tableName)
);
navigateToTable(tableName);
cy.get(databaseSelectors.idColumnHeader).verifyVisibleElement("have.text", databaseText.idColumnHeader);
cy.get(databaseSelectors.noRecordsText).verifyVisibleElement("have.text", databaseText.noRecordsText);
};
export const editTableNameAndVerifyToastMessage = (tableName, newTableName) => {
cy.get(databaseSelectors.currentTable(tableName))
.find(databaseSelectors.tableKebabIcon).invoke('show')
.trigger('mouseover')
.trigger('mousemove')
.trigger('mousedown')
.trigger('mouseup').click();
cy.get(databaseSelectors.tableEditOption).click();
cy.get(databaseSelectors.editTableHeader).verifyVisibleElement("have.text", databaseText.editTableHeader);
cy.get(databaseSelectors.tableNameLabel).verifyVisibleElement(
"have.text",
databaseText.tableNameLabel
);
cy.get(databaseSelectors.tableNameInputField).should("be.visible");
cy.clearAndType(databaseSelectors.tableNameInputField, newTableName);
cy.get(commonSelectors.buttonSelector(commonText.cancelButton))
.should("be.visible")
.and("have.text", commonText.cancelButton);
cy.get(commonSelectors.buttonSelector(commonText.saveChangesButton))
.should("be.visible")
.and("have.text", commonText.saveChangesButton);
cy.get(commonSelectors.buttonSelector(commonText.saveChangesButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
databaseText.tableEditedSuccessfullyToast(newTableName)
);
cy.get(databaseSelectors.currentTableName(newTableName)).verifyVisibleElement("have.text", newTableName);
};
export const deleteTableAndVerifyToastMessage = (tableName) => {
cy.get(databaseSelectors.currentTable(tableName))
.find(databaseSelectors.tableKebabIcon).invoke('show')
.trigger('mouseover')
.trigger('mousemove')
.trigger('mousedown')
.trigger('mouseup').click();
cy.get(databaseSelectors.tableDeleteOption).click();
// cy.on('window:confirm', (ConfirmAlertText) => {
// expect(ConfirmAlertText).to.contains(`Are you sure you want to delete the table "${tableName}"?`);
// });
cy.verifyToastMessage(
commonSelectors.toastMessage,
databaseText.tableDeletedSuccessfullyToast(tableName)
);
};
export const addNewColumnAndVerify = (columnName = [], columnDataType = [], defaultValue = true, columnDefaultValue = []) => {
cy.clearAndType(databaseSelectors.nameInputField("undefined"), columnName)
cy.get(databaseSelectors.nameInputField(columnName)).should("be.visible")
.verifyVisibleElement("have.value", columnName)
.parents(".list-group-item")
.within(() => {
cy.get(databaseSelectors.typeInputField).click();
cy.contains(`[id*="react-select-"]`, columnDataType).click();
if (defaultValue) {
cy.clearAndType(databaseSelectors.defaultInputField, columnDefaultValue)
}
cy.get(databaseSelectors.typeInputField).should("be.visible")
.verifyVisibleElement("have.text", columnDataType)
cy.get(databaseSelectors.defaultInputField).should("be.visible")
.verifyVisibleElement("have.value", columnDefaultValue)
})
};
export const createNewColumnAndVerify = (tableName, columnName, columnDataType, defaultValue = true, columnDefaultValue) => {
navigateToTable(tableName)
cy.get(createNewColumnSelectors.addNewColumnButton).should('be.visible')
.click();
cy.get(createNewColumnSelectors.createNewColumnHeader).verifyVisibleElement("have.text", createNewColumnText.createNewColumnHeader)
cy.get(createNewColumnSelectors.columnNameLabel).verifyVisibleElement("have.text", createNewColumnText.columnNameLabel);
cy.get(createNewColumnSelectors.dataTypeLabel).verifyVisibleElement("have.text", createNewColumnText.dataTypeLabel);
cy.get(createNewColumnSelectors.defaultValueLabel).verifyVisibleElement("have.text", createNewColumnText.defaultValueLabel);
cy.clearAndType(createNewColumnSelectors.columnNameInputField, columnName);
cy.get(createNewColumnSelectors.dataTypeDropdown).click()
cy.contains(`[id*="react-select-"]`, columnDataType).click();
if (defaultValue) {
cy.clearAndType(createNewColumnSelectors.defaultValueInputField, columnDefaultValue);
cy.get(createNewColumnSelectors.defaultValueInputField).should("be.visible")
.verifyVisibleElement("have.value", columnDefaultValue);
}
cy.get(createNewColumnSelectors.columnNameInputField).should("be.visible")
.verifyVisibleElement("have.value", columnName);
cy.get(createNewColumnSelectors.dataTypeDropdown).should("be.visible")
.verifyVisibleElement("contain", columnDataType);
cy.get(commonSelectors.buttonSelector(commonText.cancelButton))
.should("be.visible")
.and("have.text", commonText.cancelButton);
cy.get(commonSelectors.buttonSelector(commonText.createButton))
.should("be.visible")
.and("have.text", commonText.createButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
createNewColumnText.columnCreatedSuccessfullyToast
);
cy.get(databaseSelectors.columnHeader(columnName)).verifyVisibleElement("have.text", `${String(columnName).toLowerCase().replace(/\s+/g, "-")}`);
};
export const addNewRowAndVerify = (tableName, noDefaultValue = true, columnName = [], columnDefaultValue = []) => {
navigateToTable(tableName);
cy.get(createNewRowSelectors.addNewRowButton).click();
cy.get(createNewRowSelectors.createNewRowHeader).verifyVisibleElement("have.text", createNewRowText.createNewRowHeader);
cy.get(createNewRowSelectors.idColumnNameLabel).verifyVisibleElement("contain", databaseText.idColumnHeader);
cy.get(createNewRowSelectors.idColumnInputField).should("be.visible");
cy.get(commonSelectors.buttonSelector(commonText.cancelButton))
.should("be.visible")
.and("have.text", commonText.cancelButton);
cy.get(commonSelectors.buttonSelector(commonText.createButton))
.should("be.visible")
.and("have.text", commonText.createButton);
cy.get('body').find(".table>>>th").its('length').then(columnLength => {
if (columnLength != 2) {
for (let i = 0; i < columnName.length; i++) {
if (noDefaultValue) {
cy.clearAndType(createNewRowSelectors.columnNameInputField(columnName[i]), columnDefaultValue[i])
}
else {
cy.get(createNewRowSelectors.columnNameInputField(columnName[i]))
.invoke('val')
.then(val => {
if (val === "") {
cy.clearAndType(createNewRowSelectors.columnNameInputField(columnName[i]), columnDefaultValue[i])
}
});
}
}
}
});
cy.get(commonSelectors.buttonSelector(commonText.createButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
createNewRowText.rowCreatedSuccessfullyToast
);
cy.get('[data-cy*="-column-id-table-cell"]').should("be.visible");
};
export const verifyAllElementsOfAddTableSection = () => {
cy.get(databaseSelectors.tableNameLabel).verifyVisibleElement(
"have.text",
databaseText.tableNameLabel
);
cy.get(databaseSelectors.tableNameInputField).should("be.visible");
cy.get(databaseSelectors.addColumnsHeader).verifyVisibleElement(
"have.text",
databaseText.addColumnHeader
);
cy.get(databaseSelectors.nameLabel).verifyVisibleElement(
"have.text",
databaseText.nameLabel
);
cy.get(databaseSelectors.typeLabel).verifyVisibleElement(
"have.text",
databaseText.typeLabel
);
cy.get(databaseSelectors.defaultLabel).verifyVisibleElement(
"have.text",
databaseText.defaultLabel
);
cy.get(databaseSelectors.idInputField).should("be.visible");
cy.get(databaseSelectors.typeInputField).should("be.visible");
cy.get(databaseSelectors.defaultInputField).should("be.visible");
cy.get(databaseSelectors.addMoreColumnsButton).should("be.visible");
cy.get(commonSelectors.buttonSelector(commonText.cancelButton))
.should("be.visible")
.and("have.text", commonText.cancelButton);
cy.get(commonSelectors.buttonSelector(commonText.createButton))
.should("be.visible")
.and("have.text", commonText.createButton);
cy.get(databaseSelectors.addMoreColumnsButton).click();
cy.get(databaseSelectors.nameInputField("undefined")).should("be.visible")
.parents(".list-group-item")
.within(() => {
cy.get(databaseSelectors.typeInputField).should("be.visible");
cy.get(databaseSelectors.defaultInputField).should("be.visible");
cy.get(databaseSelectors.deleteIcon).should("be.visible").click();
});
};
export const filterOperation = (
tableName,
columnName = [],
operation = [],
value = []
) => {
navigateToTable(tableName);
cy.intercept("GET", "api/tooljet_db/organizations/**").as("dbLoad");
cy.get(filterSelectors.filterButton).should("be.visible").click();
cy.get(filterSelectors.selectColumnField).should("be.visible");
cy.get(filterSelectors.selectOperationField).should("be.visible");
cy.get(filterSelectors.valueInputField).should("be.visible");
cy.get(filterSelectors.deleteIcon).should("be.visible");
cy.get(filterSelectors.addConditionLink).should("be.visible");
cy.get(filterSelectors.selectColumnField).click();
cy.contains(`[id*="react-select-"]`, columnName[0]).click();
cy.get(filterSelectors.selectOperationField).click();
cy.contains(`[id*="react-select-"]`, operation[0]).click();
cy.clearAndType(filterSelectors.valueInputField, value[0]);
cy.wait("@dbLoad");
for (let i = 1; i < columnName.length; i++) {
cy.get(filterSelectors.addConditionLink).click();
cy.wait("@dbLoad");
cy.get(filterSelectors.selectColumnField).last().click();
cy.contains(
`[id*="react-select-"]`,
String(columnName[i]).toLowerCase()
).click();
cy.get(filterSelectors.selectOperationField).last().click();
cy.contains(`[id*="react-select-"]`, operation[i]).click();
cy.get(filterSelectors.valueInputField).last().clear().type(value[i]);
cy.get(filterSelectors.selectColumnField).last().should("have.text", String(columnName[i]).toLowerCase())
cy.get(filterSelectors.valueInputField).last().should("have.text", value[i]);
cy.wait("@dbLoad");
}
cy.get('.table-responsive').click();
cy.get(databaseSelectors.idColumnHeader).should("be.visible");
};
export const sortOperation = (tableName, columnName = [], order = []) => {
navigateToTable(tableName);
cy.get(sortSelectors.sortButton).should("be.visible").click();
cy.get(sortSelectors.selectColumnField).should("be.visible");
cy.get(sortSelectors.selectOrderField).should("be.visible");
cy.get(sortSelectors.deleteIcon).should("be.visible");
cy.get(sortSelectors.addConditionLink).should("be.visible");
cy.get(sortSelectors.selectColumnField).click();
cy.contains(`[id*="react-select-"]`, columnName[0]).click();
cy.get(sortSelectors.selectOrderField).click();
cy.contains(`[id*="react-select-"]`, order[0]).click();
for (let i = 1; i < columnName.length; i++) {
cy.get(sortSelectors.addConditionLink).click();
cy.get(sortSelectors.selectColumnField).last().click();
cy.contains(`[id*="react-select-"]`, String(columnName[i]).toLowerCase()).click();
cy.get(sortSelectors.selectOrderField).last().click();
cy.contains(`[id*="react-select-"]`, order[0]).click();
}
cy.get(sortSelectors.sortButton).click();
cy.get(databaseSelectors.idColumnHeader).should("be.visible");
};
export const deleteCondition = (selector, columnName = [], deleteIcon) => {
cy.get(selector).click();
cy.get('.card-body').eq(1).should("be.visible");
cy.log(columnName.length)
for (let i = 0; i < columnName.length; i++) {
cy.get(deleteIcon).eq(i).click();
}
};
export const deleteRowAndVerify = (tableName, rowNumber = []) => {
cy.get('body').find(".table>>tr").its('length').then(totalRowLength => {
cy.log(totalRowLength)
let rowsWithoutHeaderLength = totalRowLength - 1;
cy.log(rowsWithoutHeaderLength)
for (let i = 0; i < rowNumber.length; i++) {
cy.get(databaseSelectors.checkboxCell(rowNumber[i])).click();
}
cy.get(databaseSelectors.deleteRecordButton).should("be.visible").click();
// cy.on('window:confirm', (ConfirmText) => {
// expect(ConfirmText).to.equal('Are you sure you want to delete the selected rows?');
// })
cy.verifyToastMessage(
commonSelectors.toastMessage,
databaseText.deleteRowToast(tableName, rowNumber.length)
);
})
}

View file

@ -43,20 +43,22 @@ export const verifyElementsOfExportModal = (
cy.get(exportAppModalSelectors.modalCloseButton).should("be.visible");
};
export const createNewVersion = (newVersion = []) => {
cy.get(appVersionSelectors.createVersionLink).should("be.visible").click();
export const createNewVersion = (newVersion = [], version) => {
cy.contains(appVersionText.createNewVersion).should("be.visible").click();
verifyModal(
appVersionText.createVersion,
appVersionText.createVersion,
appVersionText.createNewVersion,
appVersionText.createNewVersion,
appVersionSelectors.createVersionInputField
);
cy.get(appVersionSelectors.createVersionButton).click();
cy.get(appVersionSelectors.createNewVersionButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
appVersionText.emptyToastMessage
);
cy.get(appVersionSelectors.createVersionInputField).click()
cy.contains(`[id*="react-select-"]`, version).click();
cy.get(appVersionSelectors.versionNameInputField).click().type(newVersion[0]);
cy.get(appVersionSelectors.createVersionButton).click();
cy.get(appVersionSelectors.createNewVersionButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
appVersionText.createdToastMessage
@ -99,4 +101,4 @@ export const exportAllVersionsAndVerify = (
}
clickOnExportButtonAndVerify(exportAppModalText.exportAll, appName);
});
};
};

View file

@ -0,0 +1,51 @@
import {
fillDataSourceTextField,
selectDataSource,
} from "Support/utils/postgreSql";
import { mongoDbText } from "Texts/mongoDb";
import { postgreSqlSelector } from "Selectors/postgreSql";
import { postgreSqlText } from "Texts/postgreSql";
export const connectMongo = () => {
selectDataSource(mongoDbText.mongoDb);
cy.clearAndType(
'[data-cy="data-source-name-input-filed"]',
mongoDbText.cypressMongoDb
);
cy.get('[data-cy="query-select-dropdown"]').type(
mongoDbText.optionConnectUsingConnectionString
);
fillDataSourceTextField(
mongoDbText.labelConnectionString,
mongoDbText.connectionStringPlaceholder,
Cypress.env("mongodb_connString"),
"contain",
{ parseSpecialCharSequences: false, delay: 0 }
);
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 10000,
}).should("have.text", postgreSqlText.labelConnectionVerified);
cy.get(postgreSqlSelector.buttonSave).click();
cy.get(postgreSqlSelector.leftSidebarDatasourceButton).click();
cy.get(postgreSqlSelector.datasourceLabelOnList)
.should("have.text", mongoDbText.cypressMongoDb)
.find("button")
.invoke("show")
.should("be.visible");
};
export const openMongoQueryEditor = (dbName = mongoDbText.cypressMongoDb) => {
cy.get(postgreSqlSelector.buttonAddNewQueries).click();
cy.get(`[data-cy="${dbName}-add-query-card"]`)
.should("contain", dbName)
.click();
};
export const selectQueryType = (type) => {
cy.get('[data-cy="query-select-dropdown"]').click().type(`${type}{enter}`);
};

View file

@ -11,11 +11,15 @@ import { postgreSqlText } from "Texts/postgreSql";
export const addQuery = (queryName, query, dbName) => {
cy.get(postgreSqlSelector.buttonAddNewQueries).click();
cy.get(`[data-cy="${dbName}-add-query-card"]`)
.should("contain", postgreSqlText.psqlName)
.should("contain", dbName)
.click();
selectQueryMode(postgreSqlText.queryModeSql, "3");
cy.get('[data-cy="query-name-label"]').realHover().then(()=>{
cy.get('[class*="breadcrum-rename-query-icon"]').click();
});
cy.get(postgreSqlSelector.queryLabelInputField).clear().type(queryName);
cy.get(postgreSqlSelector.queryInputField).should("be.visible").type(query);
cy.get(postgreSqlSelector.queryInputField).realMouseDown({ position: "center" }).realType(' ');
cy.get(postgreSqlSelector.queryInputField).clearAndTypeOnCodeMirror(query)
cy.get(postgreSqlSelector.queryCreateAndRunButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
@ -26,7 +30,7 @@ export const addQuery = (queryName, query, dbName) => {
export const addQueryOnGui = (queryName, query) => {
cy.get(postgreSqlSelector.buttonAddNewQueries).click();
cy.get('[data-cy="cypress-postgresql"]')
.should("contain", postgreSqlText.psqlName)
.should("contain", dbName)
.click();
cy.get(postgreSqlSelector.queryLabelInputField).clear().type(queryName);
@ -46,13 +50,16 @@ export const selectDataSource = (dataSource) => {
cy.get(`[data-cy='data-source-${dataSource.toLowerCase()}']`).click();
};
export const fillConnectionForm = (data) => {
export const fillConnectionForm = (data, toggle = "") => {
for (const property in data) {
cy.clearAndType(
`[data-cy="${cyParamName(property)}-text-field"]`,
`${data[property]}`
);
}
if (toggle != "") {
cy.get(toggle).click();
}
cy.get(postgreSqlSelector.buttonTestConnection).click();
cy.get(postgreSqlSelector.textConnectionVerified, {
timeout: 7000,
@ -89,13 +96,16 @@ export const openQueryEditor = (dataSourceName) => {
};
export const selectQueryMode = (mode, index = 2) => {
cy.get(`${postgreSqlSelector.querySelectDropdown}:eq(0)`).click();
cy.get(`${postgreSqlSelector.querySelectDropdown}:eq(0)`)
.scrollIntoView()
.should("be.visible")
.click();
cy.contains("[id*=react-select-]", mode).click();
};
export const addGuiQuery = (tableName, primaryKey) => {
cy.get(`${postgreSqlSelector.querySelectDropdown}:eq(1)`).click();
cy.get("#react-select-3-option-0").click();
cy.get("#react-select-5-option-0").click();
cy.get(postgreSqlSelector.tableNameInputField).type(tableName);
cy.get(postgreSqlSelector.primaryKeyColoumnInputField).type(primaryKey);

View file

@ -164,3 +164,26 @@ export const dataCsvAssertionHelper = (data) => {
});
return dataArray;
};
export const addFilter =(data=[{column:'name', operation: "contains", value: 'Sarah'}], freshFilter=false)=>{
cy.get(tableSelector.filterButton).click();
data.forEach((filter,index) => {
if(freshFilter==true){
if(index==0){cy.get(tableSelector.buttonClearFilter).click()}
cy.get(tableSelector.buttonAddFilter).click()
}
cy.get(tableSelector.filterSelectColumn(index))
.click()
.type(`${filter.column}{enter}`);
cy.get(tableSelector.filterSelectOperation(index))
.click()
.type(`${filter.operation}{enter}`);
if(filter.value){
cy.get(tableSelector.filterInput(index)).type(`{selectAll}{del}${filter.value}`);
}
});
cy.get(tableSelector.buttonCloseFilters).click()
}

View file

@ -0,0 +1,113 @@
import { appVersionText } from "Texts/exportImport";
import { appVersionSelectors } from "Selectors/exportImport";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { commonText } from "Texts/common";
import { verifyModal, closeModal } from "Support/utils/common"
import { deleteVersionSelectors, editVersionSelectors } from "Selectors/version";
import { deleteVersionText, editVersionText, releasedVersionText } from "Texts/version";
import { verifyComponent } from "Support/utils/basicComponents";
export const navigateToCreateNewVersionModal = (value) => {
cy.get(appVersionSelectors.currentVersionField(value)).should("be.visible").click();
cy.contains(appVersionText.createNewVersion).should("be.visible").click();
}
export const navigateToEditVersionModal = (value) => {
cy.get(appVersionSelectors.currentVersionField(value)).should("be.visible").click();
cy.get('[style="padding: 8px 12px;"] .row').should("be.visible")
.within(() => {
cy.get(".icon").trigger("mouseover").click();
})
}
export const verifyElementsOfCreateNewVersionModal = (version = []) => {
cy.get(appVersionSelectors.createNewVersion).verifyVisibleElement("have.text", appVersionText.createNewVersion);
cy.get(appVersionSelectors.versionNamelabel).verifyVisibleElement("have.text", appVersionText.versionNameLabel);
cy.get(appVersionSelectors.createVersionFromLabel).verifyVisibleElement("have.text", appVersionText.createVersionFromLabel);
cy.get(appVersionSelectors.versionNameInputField).should("be.visible");
cy.get(appVersionSelectors.createVersionInputField).verifyVisibleElement("have.text", version[0]);
cy.get(
commonSelectors.buttonSelector(appVersionText.createNewVersion)
).verifyVisibleElement("have.text", appVersionText.createNewVersion);
cy.get(commonSelectors.buttonSelector(commonText.cancelButton))
.should("be.visible")
.and("have.text", commonText.cancelButton);
cy.get(commonSelectors.buttonSelector(commonText.closeButton)).should(
"be.visible"
).click();
}
export const editVersionAndVerify = (currentVersion, newVersion = [], toastMessageText) => {
cy.reload();
navigateToEditVersionModal(currentVersion)
cy.get(editVersionSelectors.versionNameInputField).verifyVisibleElement("have.value", currentVersion);
cy.clearAndType(
editVersionSelectors.versionNameInputField,
newVersion[0]
);
cy.get(editVersionSelectors.saveButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
toastMessageText
);
}
export const deleteVersionAndVerify = (value, toastMessageText) => {
cy.get(appVersionSelectors.currentVersionField(value)).should("be.visible").click();
cy.contains(`[id*="react-select-"]`, value).should("be.visible")
.within(() => {
cy.get(" .app-version-list-item")
.trigger('mouseover')
.trigger("mouseenter")
.find(".app-version-delete")
.click({ force: true });
})
cy.get(deleteVersionSelectors.modalMessage).verifyVisibleElement("have.text", deleteVersionText.deleteModalText(value));
cy.get(deleteVersionSelectors.yesButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
toastMessageText
);
};
export const verifyDuplicateVersion = (newVersion = [], version) => {
cy.contains(appVersionText.createNewVersion).should("be.visible").click();
verifyModal(
appVersionText.createNewVersion,
appVersionText.createNewVersion,
appVersionSelectors.createVersionInputField
);
cy.get(appVersionSelectors.createVersionInputField).click()
cy.contains(`[id*="react-select-"]`, version).click();
cy.get(appVersionSelectors.versionNameInputField).click().type(newVersion[0]);
cy.get(appVersionSelectors.createNewVersionButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
appVersionText.versionNameAlreadyExists
);
};
export const releasedVersionAndVerify = (currentVersion) => {
cy.contains("Release").click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
releasedVersionText.releasedToastMessage(currentVersion)
);
verifyModal(
appVersionText.createNewVersion,
appVersionText.createNewVersion,
appVersionSelectors.versionNameInputField
);
cy.contains(releasedVersionText.releasedModalText).should("be.visible");
closeModal(commonText.closeButton);
cy.get(appVersionSelectors.currentVersionField(currentVersion)).should("have.class", "color-light-green");
};
export const verifyVersionAfterPreview = (currentVersion) => {
cy.get(appVersionSelectors.currentVersionField(currentVersion)).should("be.visible")
cy.get(commonWidgetSelector.previewButton).invoke('removeAttr', 'target').click();
cy.url().should('include', '/home')
verifyComponent("button1");
cy.contains(currentVersion)
};

File diff suppressed because it is too large Load diff

View file

@ -12,9 +12,10 @@
"cypress": "^12.5.1"
},
"dependencies": {
"cypress-real-events": "^1.7.6",
"moment": "^2.29.4",
"node-xlsx": "^0.21.0",
"pdf-parse": "^1.1.1",
"pg": "^8.8.0"
}
}
}

View file

@ -36,7 +36,8 @@ services:
image: tooljet-server:development
platform: linux/x86_64
depends_on:
- postgres
postgres:
condition: service_healthy
volumes:
- ./server:/app/server:delegated
- ./plugins:/app/plugins
@ -57,7 +58,8 @@ services:
env_file:
- .env
depends_on:
- postgres
postgres:
condition: service_healthy
postgres:
image: postgres:13
@ -68,6 +70,11 @@ services:
- postgres:/data/postgres
environment:
- POSTGRES_PASSWORD=postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres:

View file

@ -31,7 +31,7 @@ ENV NODE_ENV=production
COPY ./server/package.json ./server/package-lock.json ./server/
RUN npm --prefix server install
COPY ./server/ ./server/
RUN npm install -g @nestjs/cli
RUN npm install -g @nestjs/cli
RUN npm --prefix server run build
FROM debian:11
@ -41,6 +41,7 @@ RUN apt-get update -yq \
&& apt-get install -yq build-essential \
&& apt-get clean -y
RUN curl -O https://nodejs.org/dist/v18.3.0/node-v18.3.0-linux-x64.tar.xz \
&& tar -xf node-v18.3.0-linux-x64.tar.xz \
&& mv node-v18.3.0-linux-x64 /usr/local/lib/nodejs \
@ -86,8 +87,12 @@ COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
RUN chgrp -R 0 /app && chmod -R g=u /app
# Define non-sudo user
RUN useradd --create-home appuser \
&& chown -R appuser:appuser /app
USER appuser
WORKDIR /app
# Dependencies for scripts outside nestjs
RUN npm install dotenv@10.0.0 joi@17.4.1
ENTRYPOINT ["./server/entrypoint.sh"]
ENTRYPOINT ["./server/entrypoint.sh"]

View file

@ -73,7 +73,11 @@ COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
RUN chgrp -R 0 /app && chmod -R g=u /app
# Define non-sudo user
RUN useradd --create-home appuser \
&& chown -R appuser:appuser /app
USER appuser
WORKDIR /app
# Dependencies for scripts outside nestjs
RUN npm install dotenv@10.0.0 joi@17.4.1

View file

@ -5,13 +5,15 @@ title: Close modal
Use this action to close the modal that is already shown.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Open webpage](/img/actions/closemodal/closemodal.png)
<img className="screenshot-full" src="/img/actions/closemodal/closemodal2.png" alt="ToolJet - Action reference - Close modal" width="700" />
</div>

View file

@ -1,6 +1,6 @@
---
id: control-component
title: Control component
title: Control component (Component Specific Actions)
---
Control component action invokes the component specific actions. Component specific actions are the actions that are exclusive actions for a particular widget. Component specific actions can be triggered either through the event handlers or from the Run JavaScript code query.
@ -14,19 +14,20 @@ Check out the **[live demo](https://youtu.be/JIhSH3YeM3E)** of Component specifi
| Widget | Component Specific Actions |
|--------|---------------------------|
| Button | Click, Set label, Disable, Visibility, Loading |
| Text | Set text, Set Visibility |
| Text Input | Set text, Clear, Set Focus, Set Blur, Disable, Visibility |
| Text Area | Set text, Clear |
| Modal | Show, Close |
| Table | Set page, Select row, Deselect Row, Discard changes |
| Dropdown | Select option |
| Multiselect | Select option, Deselect option, Clear selection |
| Map | Set location |
| Checkbox | Set checked |
| Color picker | Set color |
| Dropdown | Select option |
| File picker | Clear files |
| Kanban | Add card, Delete card, Move card, Update card data |
| Map | Set location |
| Modal | Show, Close |
| Multiselect | Select option, Deselect option, Clear selection |
| Radio button | Select option |
| Tabs | Set tab |
| Color picker | Set color |
| File picker | Clear files |
| Table | Set page, Select row, Deselect Row, Discard changes |
| Text | Set text, Set Visibility |
| Text Area | Set text, Clear |
| Text Input | Set text, Clear, Set Focus, Set Blur, Disable, Visibility |
:::info
Currently, Component specific actions are supported only by the above listed widgets. We are working on bringing component specific actions for the remaining widgets.

View file

@ -5,12 +5,14 @@ title: Copy to clipboard
Use this action to copy the text to the clipboard.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Open webpage](/img/actions/copytoclipboard/copytoclipboard.png)
<img className="screenshot-full" src="/img/actions/copytoclipboard/copy2.png" alt="ToolJet - Action reference - Copy to clipboard" width="700" />
</div>
</div>

View file

@ -15,6 +15,7 @@ Presently, the only file type supported is `CSV`.
| Type | Type of file to be generated |
| File name | Name of the file to be generated |
| Data | Data that will be used to construct the file. Its format will depend on the file type, as specified in the following section |
| Debounce | Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300` |
### Data format for CSV

View file

@ -5,13 +5,15 @@ title: Go to app
This action allows you to open any ToolJet application when an event occurs.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Open webpage](/img/actions/gotoapp/gotoapp.png)
<img className="screenshot-full" src="/img/actions/gotoapp/gotoapp2.png" alt="ToolJet - Action reference - Open webpage" width="700" />
</div>

View file

@ -5,12 +5,14 @@ title: Logout
This action allows you to log out of the application (ToolJet).
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Logout](/img/actions/logout/logout.png)
<img className="screenshot-full" src="/img/actions/logout/logout2.png" alt="ToolJet - Action reference - Logout" width="700" />
</div>

View file

@ -5,12 +5,14 @@ title: Open webpage
You can use this action to open a webpage(on a new tab) for any event.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Open webpage](/img/actions/open-webpage/open.png)
<img className="screenshot-full" src="/img/actions/open-webpage/open2.png" alt="ToolJet - Action reference - Open webpage" width="700" />
</div>
</div>

View file

@ -5,12 +5,14 @@ title: Run Query
This action allows you to fire queries when an event occurs.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Run Query](/img/actions/run-query/run-query.png)
<img className="screenshot-full" src="/img/actions/run-query/run-query2.png" alt="ToolJet - Action reference - Run Query" width="700" />
</div>

View file

@ -25,11 +25,15 @@ This action allows you to specify a `key` and its corresponding `value` to be st
</div>
3. Select the text label we've added and set its value to the name item from localStorage
3. Select the text label we've added and set its value to the name item from localStorage.
:::info
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference -Set local storage sample app](/img/actions/localstorage/3.png)
![ToolJet - Action reference -Set local storage sample app](/img/actions/localstorage/debounce.png)
</div>

View file

@ -7,12 +7,14 @@ Page variables can only be accessed within a page on which they are created, unl
Use this action to create a variable and assign a `value` to it in the [Multipage Apps](/docs/tutorial/pages).
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/actions/page/set-page-var.png" alt="ToolJet - Action reference - Switch page" width="600"/>
<img className="screenshot-full" src="/img/actions/page/setpagevar2.png" alt="ToolJet - Action reference - Switch page" width="600"/>
</div>

View file

@ -5,12 +5,20 @@ title: Set Table Page
Use this action to change the page index in the table widget.
## Options
| Option | Description |
|--------|-------------|
| Table | Select table from the dropdown |
| Page Index | Numerical value for the page index. ex: `{{2}}` |
| Debounce | Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300` |
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Open webpage](/img/actions/settablepage/settablepage.png)
<img className="screenshot-full" src="/img/actions/settablepage/page2.png" alt="ToolJet - Action reference - Open webpage" width="700" />
</div>

View file

@ -5,12 +5,20 @@ title: Set variable
This action allows you to create a variable and assign a `value` to it.
## Options
| Option | Description |
|--------|-------------|
| Key | Name(String) of the variable through which you can access the value |
| Value | A value can be a string, number, boolean expression, array, or object |
| Debounce | Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300` |
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference -Set variable](/img/actions/setvar/setvar.png)
<img className="screenshot-full" src="/img/actions/setvar/setvar2.png" alt="ToolJet - Action reference -Set variable" width="700" />
</div>
</div>

View file

@ -7,7 +7,9 @@ This action allows you to display an alert message.
You can set a custom **message** for the alert and choose a particular alert type.
There are 4 types of alert messages - **Info**, **Success**, **Warning**, and **Danger**.
There are 4 types of alert messages - **Info**, **Success**, **Warning**, and **Error**.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
@ -15,7 +17,7 @@ You can also trigger actions from the **JavaScript code**. Check it out [here](/
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Show Alert](/img/actions/show-alert/show-alert.png)
<img className="screenshot-full" src="/img/actions/show-alert/alert2.png" alt="ToolJet - Action reference - Show Alert" width="700" />
</div>

View file

@ -5,12 +5,14 @@ title: Show modal
Use this action to show the modal for an event.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference - Open webpage](/img/actions/showmodal/showmodal.png)
<img className="screenshot-full" src="/img/actions/showmodal/showmodal2.png" alt="ToolJet - Action reference - Show modal" width="700" />
</div>

View file

@ -5,12 +5,14 @@ title: Switch Page
Use this action with different events to switch to a different page in the [Multipage App](/docs/tutorial/pages).
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/actions/page/switch-page.png" alt="ToolJet - Action reference - Switch page" width="600"/>
<img className="screenshot-full" src="/img/actions/page/switchpage2.png" alt="ToolJet - Action reference - Switch page" width="700"/>
</div>

View file

@ -5,12 +5,14 @@ title: Unset page variable
Use this action to clear the variable that was created using the set page variable action.
Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300`
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
:::
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/actions/page/set-page-var.png" alt="ToolJet - Action reference - Switch page" width="600"/>
<img className="screenshot-full" src="/img/actions/page/unsetpagevar2.png" alt="ToolJet - Action reference - Switch page" width="600"/>
</div>

View file

@ -3,7 +3,14 @@ id: unset-variable
title: Unset variable
---
This action allows you to removev the variable variable that was created using the set variable action.
This action allows you to remove the variable variable that was created using the set variable action.
## Options
| Option | Description |
|--------|-------------|
| Key | Name(String) of the variable through which you can access the value |
| Debounce | Debounce field is empty by default, you can enter a numerical value to specify the time in milliseconds after which the action will be performed. ex: `300` |
:::info
You can also trigger actions from the **JavaScript code**. Check it out [here](/docs/how-to/run-actions-from-runjs).
@ -11,6 +18,6 @@ You can also trigger actions from the **JavaScript code**. Check it out [here](/
<div style={{textAlign: 'center'}}>
![ToolJet - Action reference -Set variable](/img/actions/unsetvar/unsetvar.png)
<img className="screenshot-full" src="/img/actions/unsetvar/unsetvar2.png" alt="ToolJet - Action reference -Unset variable" width="700" />
</div>

View file

@ -21,4 +21,5 @@ ToolJet client is a ReactJS application. Client is responsible for visually edit
## Requirements
1. **Node version 14.x**
1. **Node version 18.3.0**
2. **npm version 8.11.0**

View file

@ -122,7 +122,7 @@ Example:
Let's say you need to install the `imagemagick` binary in your ToolJet server's container. You'd then need to make sure that `apt` installs `imagemagick` while building the image. The Dockerfile at `docker/server.Dockerfile.dev` for the server would then look something like this:
```
FROM node:14.17.0-buster
FROM node:18.3.0-buster AS builder
RUN apt update && apt install -y \
build-essential \

View file

@ -14,14 +14,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: v14.17.3 ) and npm (version: v7.20.0)
1.2 Install Node.js ( version: v18.3.0 ) and npm (version: v8.11.0)
```bash
brew install nvm
export NVM_DIR=~/.nvm
source $(brew --prefix nvm)/nvm.sh
nvm install 14.17.3
nvm use 14.17.3
npm install -g npm@7.20.0
nvm install 18.3.0
nvm use 18.3.0
npm install -g npm@8.11.0
```
1.3 Install Postgres

View file

@ -10,15 +10,18 @@ Follow these steps to setup and run ToolJet on Ubuntu. Open terminal and run the
1. Set up the environment
1.1 Install Node.js
1.1 Install NVM
```bash
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
```
# Required for compilation of Plugins (https://stackoverflow.com/a/44182915/19432410)
apt-get -y install build-essential gcc g++ make python3-dev
# Ensure you have the correct version of npm, or it will cause an error about fsevents.
Close and reopen your terminal to start using nvm
```bash
nvm install 18.3.0
```
Ensure you have the correct version of npm, or it will cause an error about fsevents.
```bash
npm i -g npm@8.11.0
```
@ -38,6 +41,12 @@ Follow these steps to setup and run ToolJet on Ubuntu. Open terminal and run the
Please follow the installation [PostgREST](https://postgrest.org/en/stable/install.html) guide
**Note:** Clone the GitHub repo locally using:
```bash
git clone https://github.com/ToolJet/ToolJet.git
```
2. Set up environment variables

View file

@ -38,6 +38,6 @@ For VSCode users, you can set the formatter to `ESLint` in the [**settings.json*
## Requirements
1. **Node version 14.17.3**
2. **npm version 7.20.0**
1. **Node version 18.3.0**
2. **npm version 8.11.0**

View file

@ -0,0 +1,381 @@
---
id: build-plugin-for-marketplace
title: Build a new plugin for marketplace
---
## Introduction
ToolJet marketplace is a place where you can find custom plugins and install them in your ToolJet instance. This document will help you to build a new plugin for ToolJet marketplace.
## Prerequisites
- [Node.js](https://nodejs.org/en/download/) (v18.3.0)
- [npm](https://www.npmjs.com/get-npm) (v8.11.0)
## Getting started
### 1. Enabling the marketplace for your instance
To enable the marketplace for your instance, you need to set the `ENABLE_MARKETPLACE` environment variable to `true` in your `.env` file.
Marketplacwe is disabled by default.
Once you set the environment variable, restart your ToolJet instance. You can find the instructions to run ToolJet locally [here](/docs/setup/).
Marketplace can be accessed from '/integrations' route.
### 2. Installing tooljet-cli
ToolJet marketplace uses [tooljet-cli](https://www.npmjs.com/package/@tooljet/cli) to build and publish plugins. You can install it using npm.
```bash
npm install -g tooljet-cli
# verify the installation
tooljet --version
```
### 3. Creating a new plugin - Github plugin
Let's create a new Github plugin for ToolJet marketplace, which will authenticate a user using Github Personal Access Token and will include basic operations like fetching user details, fetching repositories, fetching issues and fetching pull requests.
```bash
# create a new plugin
tooljet plugin create github
```
Provide the plugin name and select the plugin type, which is a `api` in this case.
Select `yes` when asked to create a new plugin for marketplace.
Provide the repository URL if hosted on GitHub, otherwise leave it blank.
When you create a plugin using the ToolJet CLI, an object is automatically added to the plugins.json file, which is located in the `ToolJet/server/src/assets/marketplace/` directory. This object contains metadata about the plugin, such as its name, description, version, author, and other details.
This plugins.json file serves as a registry of all the plugins that are available for use in ToolJet. When ToolJet server starts up, it reads this file and loads all the plugins that are listed in it.
:::note
It's important to note that the plugins.json file should not be manually edited as it is automatically generated by the ToolJet CLI. Any changes made to this file may cause issues with the proper functioning of the plugins in the system.
:::
All marketplace plugins are stored in the `/marketplace` directory of the ToolJet repository. You can find the Github plugin [here](https://github.com/ToolJet/ToolJet/tree/develop/marketplace/plugins/github).
The directory structure of a typical ToolJet plugin looks like this:
```bash
github/
package.json
lib/
icon.svg
index.ts
operations.json
manifest.json
```
- manifest.json should include information such as the name of plugin, description, etc.
- operations.json should include the metadata of all the operations supported by the plugin.
- index.ts is the main file. It defines a QueryService for the plugin. The QueryService handles running of queries, testing connections, caching connections, etc.
- icon.svg is the icon for the plugin.
- package.json is auto generated by the cli.
:::info
**Why do we need a manifest.json file or a operations.json file?**
The manifest.json files are consumed by a React component to create dynamic UI for connection forms by defining the schema of an API or data source. The schema includes information about the source, such as its name, type, and any exposed variables. It also includes options for authentication and other properties that can be customized by the user. The properties section defines the specific fields and their types that are required for connecting to the API or data source. The React component reads the manifest.json file and generates the necessary UI components based on the schema, allowing users to enter the required information for connecting to the source. This can include text inputs, dropdowns, checkboxes, and other UI elements, depending on the schema defined in the manifest.json file.
The operations.json file contains a schema definition for a particular data source, for example, Github. It describes the available operations and their parameters that can be used to query the data source.
A React component uses this schema to create queries in ToolJet applications to generate a UI that allows users to select the desired operation and provide the required parameters.
The component would use the properties defined in the operations.json file to create various UI elements, such as dropdowns, and input fields, and handle user interactions to create the final query. Once the user has filled in the required parameters, the component would use them to generate a query that can be executed against the data source, and return the results to the user.
In conclusion, *manifest.json* and *operations.json* files play an important role in creating dynamic UI components in ToolJet applications. These files define the schema for data sources and available operations, which is then consumed by React components to generate the necessary UI elements for users to interact with. By using these files, ToolJet enables users to easily connect to various APIs and data sources, perform queries and retrieve data in a user-friendly way.
:::
### 4. Defining the manifest.json file
We need to include the necessary options to construct the connection form.
```json
"properties": {
"credentials": {
"label": "Authentication",
"key": "auth_type",
"type": "dropdown-component-flip",
"description": "Single select dropdown for choosing credentials",
"list": [
{
"value": "personal_access_token",
"name": "Use Personal Access Token"
}
]
},
"personal_access_token": {
"token": {
"label": "Token",
"key": "personal_token",
"type": "password",
"description": "Enter personal access token",
"hint": "You can generate a personal access token from your Github account settings."
}
}
}
```
It includes information about authentication options, specifically a dropdown to choose a type of credentials and a field to enter a personal access token. The label, key, type, description, and hint properties are used to define the specific fields and their types required for connecting to the API or data source.
### 5. Defining the operations.json file
```json
"properties": {
"operation": {
"label": "Operation",
"key": "operation",
"type": "dropdown-component-flip",
"description": "Single select dropdown for operation",
"list": [
{
"value": "get_user_info",
"name": "Get user info"
},
{
"value": "get_repo",
"name": "Get repository"
},
{
"value": "get_repo_issues",
"name": "Get repository issues"
},
{
"value": "get_repo_pull_requests",
"name": "Get repository pull requests"
}
]
},
"get_user_info": {
"username": {
"label": "Username",
"key": "username",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter username",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "Enter username"
}
},
"get_repo": {
"owner": {
"label": "Owner",
"key": "owner",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter owner name",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "developer"
},
"repo": {
"label": "Repository",
"key": "repo",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter repository name",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "tooljet"
}
},
"get_repo_issues": {
"owner": {
"label": "Owner",
"key": "owner",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter owner name",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "developer"
},
"repo": {
"label": "Repository",
"key": "repo",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter repository name",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "tooljet"
},
"state": {
"label": "State",
"key": "state",
"className": "codehinter-plugins col-4",
"type": "dropdown",
"description": "Single select dropdown for choosing state",
"list": [
{
"value": "open",
"name": "Open"
},
{
"value": "closed",
"name": "Closed"
},
{
"value": "all",
"name": "All"
}
]
}
},
"get_repo_pull_requests": {
"owner": {
"label": "Owner",
"key": "owner",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter owner name",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "developer"
},
"repo": {
"label": "Repository",
"key": "repo",
"type": "codehinter",
"lineNumbers": false,
"description": "Enter repository name",
"width": "320px",
"height": "36px",
"className": "codehinter-plugins",
"placeholder": "tooljet"
},
"state": {
"label": "State",
"key": "state",
"type": "dropdown",
"className": "codehinter-plugins col-4",
"description": "Single select dropdown for choosing state",
"list": [
{
"value": "open",
"name": "Open"
},
{
"value": "closed",
"name": "Closed"
},
{
"value": "all",
"name": "All"
}
]
}
}
}
```
The operations.json file defines the operations that can be performed on the data source. It includes information about the operation type, the fields required to perform the operation, and the type of each field. The label, key, type, description, and hint properties are used to define the specific fields and their types required for connecting to the API or data source.
### 6. Add the npm package of Gitub to the plugin dependencies
```bash
# change directory to the plugin directory and install the npm package
cd plugins/github
npm i octokit --workspace=@tooljet-marketplace/github
```
:::info
Steps to install npm package to a plugin
```bash
npm i <npm-package-name> --workspace=<plugin-name-in-package-json>
```
The command `npm i <npm-package-name> --workspace=<plugin-name-in-package-json>` is used to install a specific npm package into a particular workspace of a multi-package repository.
The *--workspace* flag is used to specify the workspace where the package should be installed. In this case, we are installing the package in the *@tooljet-marketplace/github* workspace.
:::
### 7. Implement the query execution logic in index.ts
The QueryService for the Github plugin handles the logic for running queries in index.ts. The QueryService receives the metadata of the data source, including the credentials and configurations for connecting and parameters for the query that was run.
For the Github datasource, the sourceOptions will include the credentials required for authentication, such as the personal access token. The queryOptions will have the configurations and parameters for the specific query, including the operation to be performed, such as getting the list of repositories for a specific user.
The QueryService will use this information to create and execute the necessary API requests against the Github API. The resulting data will be returned to the caller, which can then be further processed as required.
Create a new file query_operations.ts in the plugins/github/src directory and add the following code to it.
```typescript
import { Octokit } from 'octokit'
import { QueryOptions } from './types'
export async function getUserInfo(octokit: Octokit, options: QueryOptions): Promise<object> {
const { data } = await octokit.request(
'GET /users/{username}',
{
username: options.username
}
);
return data;
}
export async function getRepo(octokit: Octokit, options: QueryOptions): Promise<object> {
const { data } = await octokit.request(
'GET /repos/{owner}/{repo}',
{
owner: options.owner,
repo: options.repo
}
);
return data;
}
export async function getRepoIssues(octokit: Octokit, options: QueryOptions): Promise<object> {
const { data } = await octokit.request(
'GET /repos/{owner}/{repo}/issues',
{
owner: options.owner,
repo: options.repo,
state: options.state || 'all'
}
);
return data;
}
export async function getRepoPullRequests(octokit: Octokit, options: QueryOptions): Promise<object> {
const { data } = await octokit.request(
'GET /repos/{owner}/{repo}/pulls',
{
owner: options.owner,
repo: options.repo,
state: options.state || 'all'
}
);
return data;
}
```
The query_operations.ts file contains the functions that will be used to execute the queries. The functions will be called by the QueryService in index.ts.
The Github class has three methods:
- run: This method is called when a query needs to be executed. It takes in *sourceOptions* and *queryOptions* as input, which represent the source metadata and the query configuration, respectively. The run method uses the octokit library to make API requests to the GitHub API and returns the result of the query in a QueryResult object.
- testConnection: When a new data source is being added to a ToolJet application, the connection can be tested.
This method is called when a connection needs to be tested. It takes in sourceOptions as input, which represents the source metadata. The testConnection method tests the connection by attempting to get the authenticated user and returns a ConnectionTestResult object that indicates whether the connection was successful or not.
:::note
Every data source might not have a way to test connection. If not applicable for your data source, you can disable the test connection feature by adding "customTesting": true, to the manifest.json of your plugin.
::
- getConnection: This method is a helper method that returns an authenticated octokit client that is used to make requests to the GitHub API. It takes in sourceOptions as input, which represents the source metadata, and returns an authenticated octokit client.

View file

@ -174,6 +174,9 @@ actions.goToApp('slug',queryparams)
```javascript
actions.showAlert(alert type , message ) // alert types are info, success, warning, and danger
ex:
actions.showAlert('error' , 'This is an error' )
```
<div style={{textAlign: 'center'}}>

View file

@ -11,9 +11,9 @@ Both the ToolJet server and client requires some environment variables to start
#### ToolJet host ( required )
| variable | description |
| ------------ | --------------------------------------------------------------- |
| TOOLJET_HOST | the public URL of ToolJet client ( eg: https://app.tooljet.com ) |
| variable | description |
| ------------ | ---------------------------------------------------------------- |
| TOOLJET_HOST | the public URL of ToolJet client ( eg: https://app.tooljet.com ) |
#### Lockbox configuration ( required )
@ -46,6 +46,11 @@ ToolJet server uses PostgreSQL as the database.
If you are using docker-compose setup, you can set PG_HOST as `postgres` which will be DNS resolved by docker
:::
:::info
If you intent you use the DB connection url and if the connection does not support ssl. Please use the below format using the variable DATABASE_URL.
`postgres://username:password@hostname:port/database_name?sslmode=disable`
:::
### Disable database and extension creation (optional)
ToolJet by default tries to create database based on `PG_DB` variable set and additionally my try to create postgres extensions. This requires the postgres user to have CREATEDB permission. If this cannot be granted you can disable this behaviour by setting `PG_DB_OWNER` as `false` and will have to manually run them.
@ -58,38 +63,38 @@ Self-hosted version of ToolJet pings our server to fetch the latest product upda
Use this environment variable to enable/disable the feature that allows you to add comments on the canvas.
| variable | value |
| -------- | ---------------------- |
| COMMENT_FEATURE_ENABLE | `true` or `false` |
| variable | value |
| ---------------------- | ----------------- |
| COMMENT_FEATURE_ENABLE | `true` or `false` |
#### Multiplayer feature enable ( optional )
Use this environment variable to enable/disable the feature that allows users to collaboratively work on the canvas.
| variable | value |
| -------- | ---------------------- |
| ENABLE_MULTIPLAYER_EDITING | `true` or `false` |
| variable | value |
| -------------------------- | ----------------- |
| ENABLE_MULTIPLAYER_EDITING | `true` or `false` |
#### Marketplace feature enable ( optional )
Use this environment variable to enable/disable the feature that allows users to use the [marketplace](/docs/marketplace).
| variable | value |
| -------- | ---------------------- |
| ENABLE_MARKETPLACE_FEATURE | `true` or `false` |
| variable | value |
| -------------------------- | ----------------- |
| ENABLE_MARKETPLACE_FEATURE | `true` or `false` |
#### Enable ToolJet Database ( optional )
| variable | description |
| ------------------ | -------------------------------------------- |
| ENABLE_TOOLJET_DB | `true` or `false` |
| TOOLJET_DB | Default value is `tooljet_db` |
| TOOLJET_DB_HOST | database host |
| TOOLJET_DB_USER | database username |
| TOOLJET_DB_PASS | database password |
| TOOLJET_DB_PORT | database port |
| PGRST_JWT_SECRET | JWT token client provided for authentication |
| PGRST_HOST | postgrest database host |
| variable | description |
| ----------------- | -------------------------------------------- |
| ENABLE_TOOLJET_DB | `true` or `false` |
| TOOLJET_DB | Default value is `tooljet_db` |
| TOOLJET_DB_HOST | database host |
| TOOLJET_DB_USER | database username |
| TOOLJET_DB_PASS | database password |
| TOOLJET_DB_PORT | database port |
| PGRST_JWT_SECRET | JWT token client provided for authentication |
| PGRST_HOST | postgrest database host |
Use `ENABLE_TOOLJET_DB` to enable/disable the feature that allows users to work with inbuilt data store to build apps with. Inorder to set it up, [follow the instructions here](/docs/tooljet-database#enabling-the-tooljet-database-for-your-instance).
@ -98,13 +103,18 @@ When this feature is enabled, the database name provided for `TOOLJET_DB` will b
Incase you want to trigger it manually, use the command `npm run db:create` on ToolJet server.
:::
:::info
If you intent you use the DB connection url and if the connection does not support ssl. Please use the below format using the variable TOOLJET_DB_URL.
`postgres://username:password@hostname:port/database_name?sslmode=disable`
:::
#### Server Host ( optional )
You can specify a different server for backend if it is hosted on another server.
| variable | value |
| -------- | ---------------------- |
| SERVER_HOST | Configure a hostname for the server as a proxy pass. If no value is set, it defaults to `server`. |
| variable | value |
| ----------- | ------------------------------------------------------------------------------------------------- |
| SERVER_HOST | Configure a hostname for the server as a proxy pass. If no value is set, it defaults to `server`. |
#### Disable Multi-Workspace ( optional )
@ -180,24 +190,24 @@ Specify application monitoring vendor. Currently supported values - `sentry`.
#### SENTRY DNS ( optional )
| variable | description |
| ---------- | ----------------------------------------- |
| SENTRY_DNS | DSN tells a Sentry SDK where to send events so the events are associated with the correct project |
| variable | description |
| ---------- | ------------------------------------------------------------------------------------------------- |
| SENTRY_DNS | DSN tells a Sentry SDK where to send events so the events are associated with the correct project |
#### SENTRY DEBUG ( optional )
Prints logs for sentry.
| variable | description |
| ---------- | ----------------------------------------- |
| variable | description |
| ------------ | ------------------------------------------- |
| SENTRY_DEBUG | `true` or `false`. Default value is `false` |
#### Server URL ( optional)
This is used to set up for CSP headers and put trace info to be used with APM vendors.
| variable | description |
| ------------------ | ----------------------------------------------------------- |
| variable | description |
| ------------------ | ------------------------------------------------------------ |
| TOOLJET_SERVER_URL | the URL of ToolJet server ( eg: https://server.tooljet.com ) |
#### RELEASE VERSION ( optional)
@ -208,34 +218,35 @@ Once set any APM provider that supports segregation with releases will track it.
Tooljet needs to be configured for custom CA certificate to be able to trust and establish connection over https. This requires you to configure an additional env var `NODE_EXTRA_CA_CERTS` to have absolute path to your CA certificates. This file named `cert.pem` needs to be in PEM format and can have more than one certificates.
| variable | description |
| ------------------ | ----------------------------------------------------------------- |
| variable | description |
| ------------------- | ------------------------------------------------------------------ |
| NODE_EXTRA_CA_CERTS | absolute path to certificate PEM file ( eg: /ToolJet/ca/cert.pem ) |
#### Disable telemetry ( optional )
Pings our server to update the total user count every 24 hours. You can disable this by setting the value of `DISABLE_TOOLJET_TELEMETRY` environment variable to `true`. This feature is enabled by default.
#### Password Retry Limit (Optional)
The maximum retry limit of login password for a user is by default set to 5, account will be locked after 5 unsuccessful login attempts. Use the variables mentioned below to control this behavior:
| variable | description |
| ------------------------------------- | ----------------------------------------------------------- |
| DISABLE_PASSWORD_RETRY_LIMIT | (true/false) To disable the password retry check, if value is `true` then no limits for password retry |
| PASSWORD_RETRY_LIMIT | To change the default password retry limit (5) |
| variable | description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------ |
| DISABLE_PASSWORD_RETRY_LIMIT | (true/false) To disable the password retry check, if value is `true` then no limits for password retry |
| PASSWORD_RETRY_LIMIT | To change the default password retry limit (5) |
#### SSO Configurations (Optional)
Configurations for instance level SSO. Valid only if `DISABLE_MULTI_WORKSPACE` is not `true`.
| variable | description |
| ------------------------------------- | ----------------------------------------------------------- |
| SSO_GOOGLE_OAUTH2_CLIENT_ID | Google OAuth client id |
| SSO_GIT_OAUTH2_CLIENT_ID | GitHub OAuth client id |
| SSO_GIT_OAUTH2_CLIENT_SECRET | GitHub OAuth client secret |
| SSO_GIT_OAUTH2_HOST | GitHub OAuth host name if GitHub is self hosted |
| SSO_ACCEPTED_DOMAINS | comma separated email domains that supports SSO authentication |
| SSO_DISABLE_SIGNUPS | Disable user sign up if authenticated user does not exist |
| variable | description |
| ---------------------------- | -------------------------------------------------------------- |
| SSO_GOOGLE_OAUTH2_CLIENT_ID | Google OAuth client id |
| SSO_GIT_OAUTH2_CLIENT_ID | GitHub OAuth client id |
| SSO_GIT_OAUTH2_CLIENT_SECRET | GitHub OAuth client secret |
| SSO_GIT_OAUTH2_HOST | GitHub OAuth host name if GitHub is self hosted |
| SSO_ACCEPTED_DOMAINS | comma separated email domains that supports SSO authentication |
| SSO_DISABLE_SIGNUPS | Disable user sign up if authenticated user does not exist |
## ToolJet client
@ -243,29 +254,26 @@ Configurations for instance level SSO. Valid only if `DISABLE_MULTI_WORKSPACE` i
This is required when client is built separately.
| variable | description |
| ------------------ | ----------------------------------------------------------- |
| variable | description |
| ------------------ | ------------------------------------------------------------ |
| TOOLJET_SERVER_URL | the URL of ToolJet server ( eg: https://server.tooljet.com ) |
#### Server Port ( optional)
This could be used to for local development, it will set the server url like so: `http://localhost:<TOOLJET_SERVER_PORT>`
| variable | description |
|---------------------|-----------------------------------------|
| ------------------- | --------------------------------------- |
| TOOLJET_SERVER_PORT | the port of ToolJet server ( eg: 3000 ) |
#### Asset path ( optionally required )
This is required when the assets for the client are to be loaded from elsewhere (eg: CDN).
This can be an absolute path, or relative to main HTML file.
| variable | description |
| ------------------ | ----------------------------------------------------------- |
| ASSET_PATH | the asset path for the website ( eg: https://app.tooljet.com/) |
| variable | description |
| ---------- | -------------------------------------------------------------- |
| ASSET_PATH | the asset path for the website ( eg: https://app.tooljet.com/) |
#### Serve client as a server end-point ( optional )
@ -274,11 +282,11 @@ If you intend to use client separately then can set `SERVE_CLIENT` to `false`.
## PostgREST server (Optional)
| variable | description |
| ------------------ | ----------------------------------------------- |
| PGRST_JWT_SECRET | JWT token client provided for authentication |
| PGRST_DB_URI | database connection string for tooljet database |
| PGRST_LOG_LEVEL | `info` |
| variable | description |
| ---------------- | ----------------------------------------------- |
| PGRST_JWT_SECRET | JWT token client provided for authentication |
| PGRST_DB_URI | database connection string for tooljet database |
| PGRST_LOG_LEVEL | `info` |
If you intent to make changes in the above configuration. Please refer [PostgREST configuration docs](https://postgrest.org/en/stable/configuration.html#environment-variables).

View file

@ -47,7 +47,7 @@ If you intend to use this feature, you'd have to set up and deploy PostgREST ser
1. Setup PostgREST server
```bash
kubectl apply -f https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/GKE/postgrest.yaml
kubectl apply -f https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/AKS/postgrest.yaml
```
2. Update ToolJet deployment with the appropriate env variables [here](https://github.com/ToolJet/ToolJet/blob/chore/main/kubernetes/GKE/deployment.yaml#L62) and apply the changes.
2. Update ToolJet deployment with the appropriate env variables [here](https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/AKS/deployment.yaml) and apply the changes.

View file

@ -76,4 +76,4 @@ If you intend to use this feature, you'd have to set up and deploy PostgREST ser
kubectl apply -f https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/GKE/postgrest.yaml
```
2. Update ToolJet deployment with the appropriate env variables [here](https://github.com/ToolJet/ToolJet/blob/chore/main/kubernetes/GKE/deployment.yaml#L62) and apply the changes.
2. Update ToolJet deployment with the appropriate env variables [here](https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/GKE/deployment.yaml) and apply the changes.

View file

@ -0,0 +1,68 @@
---
id: openshift
title: Openshift
---
# Deploying ToolJet on Openshift
:::info
You should setup a PostgreSQL database manually to be used by ToolJet.
:::
Follow the steps below to deploy ToolJet on Openshift.
1. Setup a PostgreSQL database ToolJet uses a postgres database as the persistent storage for storing data related to users and apps. We do not have plans to support other databases such as MySQL.
2. Create a Kubernetes secret with name `server`. For the minimal setup, ToolJet requires `pg_host`, `pg_db`, `pg_user`, `pg_password`, `secret_key_base` & `lockbox_key` keys in the secret.
Read **[environment variables reference](https://docs.tooljet.com/docs/setup/env-vars)**
3. Once you have logged into the Openshift developer dashboard click on `+Add` tab. Select import YAML from the local machine.
:::note
When entering one or more files and use --- to separate each definition
:::
Copy paste depolyment.yaml to the online editor
```
https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/openshift/deployment.yaml
```
Copy paste the service.yaml to the online editor
```
https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/openshift/service.yaml
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/setup/openshift/online-yaml-editor.png" alt="online yaml editor" />
</div>
Once you have added the files click on create.
:info
If there are self signed HTTPS endpoints that Tooljet needs to connect to, please make sure that `NODE_EXTRA_CA_CERTS` environment variable is set to the absolute path containing the certificates. You can make use of kubernetes secrets to mount the certificate file onto the containers.
:::
4. Navigate to topology tab and use the visual connector to establish the connect between tooljet-deployment and postgresql as shown in the screenshot below.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/setup/openshift/toplogy.png" alt="toplogy" />
</div>
## ToolJet Database
You can know more about tooljet database [here](https://docs.tooljet.com/docs/tooljet-database)
If you intend to use this feature, you'd have to set up and deploy PostgREST server which helps querying ToolJet Database. Please [follow the instructions here](https://docs.tooljet.com/docs/setup/env-vars#tooljet-database) for additional environment variables configuration to be done.
```
https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/openshift/postgrest.yaml
```

View file

@ -204,6 +204,16 @@ To add a new row to the existing table data, click on the **Add new row** button
</div>
### Edit row
To edit the rows from the ToolJet database dashboard, click on the **Edit row** button. A drawer will open from the right from where first you need to **select the id** of the row to be edited from the dropdown and then you can edit the cell values of the selected row.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/v2-beta/database/editrowg.gif" alt="ToolJet Database editor" width="700" />
</div>
### Delete records
To delete one or many records/rows, select on the checkbox at the right of the record or records that you want to delete. As soon as you select a single record, the button to delete record will appear on the top, click on the **Delete record** button to delete the selected records.

View file

@ -0,0 +1,100 @@
---
id: bounded-box
title: Bounded Box
---
# Bounded box
A bounded box is an infinitely customizable image annotation component that can be used to select and tag areas of an image. It supports selection using specific points (landmarking) or draw rectangular areas (bounding boxes).
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/bounded-box/bounded-box.png" alt="Bounded Box" />
</div>
## Properties
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/bounded-box/prop.png" alt="Bounded Box" width="300"/>
</div>
### Image URL
The bounding box required an image to display, enter the URL of the image to display it on the component.
### Selector
The bounded box support selection using:
- **Rectangle**
- **Point**
You can also click on the **Fx** to set the value programmatically.
### List of labels
This property will include the list of label that will be displayed in the dropdown while selection in the bounded-box. This property requires the label in array format.
## Events
To add an event to a bounded-box, click on the component handle to open its properties on the right. Go to the **Events** accordion and click on **Add handler**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/bounded-box/onchange.png" alt="Button group events" width="600"/>
</div>
### On change
On change event is triggered when the label from the dropdown in the selector is changed in the bounded box. Just like any other event on ToolJet, you can set multiple handlers for on-change event.
:::info
Check [Action Reference](/docs/category/actions-reference) docs to get the detailed information about all the **Actions**.
:::
## General
### Tooltip
A Tooltip is often used to specify extra information about something when the user hovers the mouse pointer over the widget.
Under the <b>General</b> accordion, you can set the value in the string format. Hovering over the component will display the string as the tooltip.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/bounded-box/tooltip.png" alt="Button group Tooltip" width="300"/>
</div>
## Layout
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/button-group/layout.png" alt="Button group layout" width="300"/>
</div>
| Layout | description | Expected value |
| ----------- | ----------- | ------------ |
| Show on desktop | Toggle on or off to display desktop view. | You can programmatically determine the value by clicking on `Fx` to set the value `{{true}}` or `{{false}}` |
| Show on mobile | Toggle on or off to display mobile view. | You can programmatically determine the value by clicking on `Fx` to set the value `{{true}}` or `{{false}}` |
## Styles
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/bounded-box/styles.png" alt="Bounded box properties" width="300"/>
</div>
| Style | Description |
| ----------- | ----------- |
| Visibility | Toggle on or off to control the visibility of the component. You can programmatically change its value by clicking on the `Fx` button next to it. If `{{false}}` the component will not be visible when the app is loaded. By default, it's set to `{{true}}`. |
| Disable | Toggle on to disable the widget. You can programmatically change its value by clicking on the `Fx` button next to it, if set to `{{true}}`, the component will be disabled and becomes non-functional. By default, its value is set to `{{false}}`. |
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::

View file

@ -41,9 +41,9 @@ Check [Action Reference](/docs/category/actions-reference) docs to get the detai
| Properties | description | Expected value |
| ----------- | ----------- | -------------- |
| label | label is used to set the heading of the button group. | Any **String** value |
| values |Values for button group items. | **Array** of strings |
| labels | It can be used to set the labels of the button group items. | Array of **strings**|
| Default selected | Initial selected values can be set using this. | Array of **strings** |
| values |Values for button group items. | **Array** of strings and numbers |
| labels | It can be used to set the labels of the button group items. | **Array** of strings and numbers |
| Default selected | Initial selected values can be set using this. | **Array** of strings and numbers |
| Enable multiple selection | Toggle this to allow multiple button selection. | Toggle to true/false |
### General

View file

@ -120,6 +120,222 @@ The result will be like this:
</div>
### Using Plotly JSON chart schema
In the **JSON description**, the value needs to be the `data` array with x and y axis values and at the end we need to specify the `type`. let's take a look at the examples for different chart types.
#### Line
```bash
{
"data": [
{
"x": [
"Jan",
"Feb",
"Mar"
],
"y": [
100,
80,
40
],
"type": "line"
},
{
"x": [
"Jan",
"Feb",
"Mar"
],
"y": [
300,
30,
20
],
"type": "line"
}
]
}
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/chart/plotly/line.png" alt="ToolJet - Widget Reference - Chart" />
</div>
#### Bar
```bash
{
"data": [
{
"name": "SF Zoo",
"type": "bar",
"x": [
20,
14,
23
],
"y": [
"giraffes",
"orangutans",
"monkeys"
],
"marker": {
"line": {
"color": "rgba(55, 128, 191, 1.0)",
"width": 1
},
"color": "rgba(55, 128, 191, 0.6)"
},
"orientation": "h"
},
{
"name": "LA Zoo",
"type": "bar",
"x": [
12,
18,
29
],
"y": [
"giraffes",
"orangutans",
"monkeys"
],
"marker": {
"line": {
"color": "rgba(255, 153, 51, 1.0)",
"width": 1
},
"color": "rgba(255, 153, 51, 0.6)"
},
"orientation": "h"
}
],
"layout": {
"barmode": "stack"
},
"frames": []
}
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/chart/plotly/bar2.png" alt="ToolJet - Widget Reference - Chart" />
</div>
#### Area
```bash
{
"data": [
{
"uid": "babced",
"fill": "tonexty",
"mode": "none",
"name": "Col2",
"type": "scatter",
"x": [
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01"
],
"y": [
"17087182",
"29354370",
"38760373",
"40912332",
"51611646",
"64780617",
"85507314",
"121892559",
"172338726",
"238027855",
"206956723",
"346004403",
"697089489",
"672985183",
"968882453",
"863105652",
"1068513050"
],
"fillcolor": "rgb(224, 102, 102)"
}
],
"layout": {
"title": "Total Number of Websites",
"width": 800,
"xaxis": {
"type": "date",
"range": [
946702800000,
1451624400000
],
"title": "Source: <a href=\"http://www.scribblrs.com/\">Scribblrs</a><br>Source: <a href=\"http://www.internetlivestats.com/total-number-of-websites/\">Internet Live Stats</a>",
"showgrid": false,
"autorange": true,
"tickformat": "%Y"
},
"yaxis": {
"type": "linear",
"range": [
0,
1124750578.9473684
],
"title": "",
"autorange": true
},
"height": 500,
"autosize": false
},
"frames": []
}
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/chart/plotly/area.png" alt="ToolJet - Widget Reference - Chart" />
</div>
#### Few more exmaples:
**Link to JSON description:** https://raw.githubusercontent.com/plotly/plotly.js/master/test/image/mocks/0.json
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/chart/plotly/chart1.png" alt="ToolJet - Widget Reference - Chart" />
</div>
**Link to JSON description:** https://raw.githubusercontent.com/plotly/plotly.js/master/test/image/mocks/12.json
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/chart/plotly/chart2.png" alt="ToolJet - Widget Reference - Chart" />
</div>
:::tip
Check the **[Plotly documentation](https://plotly.com/chart-studio-help/json-chart-schema/#more-examples)** to explore the all type of charts available.
:::
## Marker color
Modify the color of marker using the color picker or by providing a `HEX color code`.

View file

@ -123,3 +123,18 @@ You can align the text inside the widget in following ways: left, right, center,
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed variables
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/dropdown/variables.png" alt="ToolJet - Widget Reference - Dropdown widget" />
</div>
| Variable | Description |
| -------- | ----------- |
| Value | This variable holds the value of the currently selected item on the dropdown. Value can be accesed using `{{components.dropdown1.value}}` |
| searchText | This variable is initially empty and will hold the value whenever the user searches on the dropdown. searchText's value can be accesed using`{{components.dropdown1.searchText}}` |
| label | The variable label holds the label name of the dropdown. label's value can be accesed using`{{components.dropdown1.searchText}}` |
| optionLabels | The optionLabels holds the option labels for the values of the dropdown. optionLabels can be accesed using`{{components.dropdown1.optionLabels}}` for all the option labels in the array form or `{{components.dropdown1.optionLabels[0]}}` for particular option label |

View file

@ -9,7 +9,7 @@ Kanban widget allows you to visually organize and prioritize your tasks with a t
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/kanban/kanban.png" alt="ToolJet - Widget Reference - Kanban widget" />
<img className="screenshot-full" src="/img/widgets/kanban/kanban2/kanban2.png" alt="ToolJet - Widget Reference - Kanban widget" />
</div>
@ -17,42 +17,66 @@ Kanban widget allows you to visually organize and prioritize your tasks with a t
To add an event, click on the widget handle to open the widget properties on the right sidebar. Go to the **Events** section and click on **Add handler**.
- **[Card added](#card-added)**
- **[On update](#on-update)**
- **[On add card click](#on-add-card-click)**
- **[Card removed](#card-removed)**
- **[Card added](#card-added)**
- **[Card moved](#card-moved)**
- **[Card selected](#card-selected)**
- **[Card updated](#card-updated)**
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/kanban/kanban2/events.png" alt="ToolJet - Widget Reference - Kanban widget" width="700" />
</div>
Just like any other event on ToolJet, you can set multiple handlers for any of the above mentioned events.
:::info
Check [Action Reference](/docs/category/actions-reference) docs to get the detailed information about all the **Actions**.
Check the **[Component Specific Action](/docs/next/actions/control-component)** available for Kanban.
:::
<div style={{textAlign: 'center'}}>
### On Update
On update event is triggered whenever the card data (id, title, description, or columnID) is updated using the component specific actions.
<img className="screenshot-full" src="/img/widgets/kanban/kanban-events.png" alt="ToolJet - Widget Reference - Kanban widget" />
### On add card click
This event is triggered whenever the **Add card** button on the kanban is clicked.
</div>
### Card removed
This event is triggered whenever the card is **deleted** from the kanban by dragging it into the bottom delete box or using component specific action.
### Card added
This event is triggered whenever a card is **added** on the kanban using the component specific action.
### Card moved
This event is triggered whenever the card's position is changed on the kanban or using the component specific action.
### Card selected
This event is triggered whenever a card is clicked to open the modal.
## Properties
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/kanban/properties.png" alt="ToolJet - Widget Reference - Kanban widget" />
<img className="screenshot-full" src="/img/widgets/kanban/kanban2/props.png" alt="ToolJet - Widget Reference - Kanban widget" width="300"/>
</div>
:::caution
Please keep in mind that you need to provide an `id` for each card in the `Card data` field <br />
and this `id` must be of type string.
- It is mandatory to provide `id` for each column in the `column data` field. The `id` can be of type `string` or `number`.
- It is mandatory to provide `id`, and `columnId` for each card in the `Card data` field. The `id` and `columnId` can be of type `string` or `number`.
:::
| Properties | description | Expected value |
| ----------- | ----------- | ----------- |
| Columns | Enter the columns data - `id` and `title` in the form of array of objects or from a query that returns an array of objects. | `{{[{ "id": "1", "title": "to do" },{ "id": "2", "title": "in progress" },{ "id": "2", "title": "Completed" }]}}` or `{{queries.xyz.data}}` |
| Card data | Enter the cards data - `id`, `title` and `columnId` in the form of array of objects or from a query that returns an array of objects. | `{{[{ id: "01", title: "one", columnId: "1" },{ id: "02", title: "two", columnId: "1" },{ id: "03", title: "three", columnId: "2" }]}}` or `{{queries.abc.data}}` |
| Enable Add Card | This property allows you to show or hide the `Add Cards` button at the bottom of every column. | By default its enabled, you can programmatically set `{{true}}` or `{{false}}` enable/disable button by clicking on the `Fx` next to it |
| Column Data | Enter the columns data - `id` and `title` in the form of array of objects or from a query that returns an array of objects. | `{{[{ "id": "c1", "title": "to do" },{ "id": "c2", "title": "in progress" },{ "id": "c3", "title": "Completed" }]}}` or `{{queries.xyz.data}}` |
| Card Data | Enter the cards data - `id`, `title` and `columnId` in the form of array of objects or from a query that returns an array of objects. | `{{[{ id: "r1", title: "Title 1", description: "Description 1", columnId: "c1" },{ id: "r2", title: "Title 2", description: "Description 2", columnId: "c2" },{ id: "r3", title: "Title 3", description: "Description 3",columnId: "c3" }]}}` or `{{queries.abc.data}}` |
| Card Width | Set the width of the card | This property expects a numerical value. By default, the value is set to `{{302}}` |
| Card Height | Set the width of the card | This property expects a numerical value. By default, the value is set to `{{100}}` |
| Enable Add Card | This property allows you to show or hide the **+Add Cards** button on the Kanban. | By default its enabled, you can programmatically set value to `{{true}}` or `{{false}}` to enable/disable button by clicking on the `Fx` next to it |
| Show Delete button | This property allows you to show or hide the **Drop here to delete cards** section at the bottom of the kanban. | By default its enabled, you can programmatically set value to `{{true}}` or `{{false}}` to enable/disable button by clicking on the `Fx` next to it |
## General
### Tooltip
@ -63,7 +87,7 @@ Under the <b>General</b> accordion, you can set the value in the string format.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooltip.png" alt="ToolJet - Widget Reference - Kanban widget" />
<img className="screenshot-full" src="/img/widgets/kanban/kanban2/tooltip.png" alt="ToolJet - Widget Reference - Kanban widget" width="400"/>
</div>
@ -71,7 +95,7 @@ Under the <b>General</b> accordion, you can set the value in the string format.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/kanban/layout.png" alt="ToolJet - Widget Reference - Kanban widget" />
<img className="screenshot-full" src="/img/widgets/kanban/kanban2/layout.png" alt="ToolJet - Widget Reference - Kanban widget" width="400"/>
</div>
@ -84,7 +108,7 @@ Under the <b>General</b> accordion, you can set the value in the string format.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/kanban/styles.png" alt="ToolJet - Widget Reference - Kanban widget" />
<img className="screenshot-full" src="/img/widgets/kanban/kanban2/styles.png" alt="ToolJet - Widget Reference - Kanban widget" width="400"/>
</div>
@ -92,23 +116,22 @@ Under the <b>General</b> accordion, you can set the value in the string format.
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Disable | If disabled or set to `{{false}}` the widget will be locked and becomes non-functional. By default, its disabled i.e. its value is set to `{{true}}` . |
| Visibility | This is to control the visibility of the widget. If `{{false}}`/disabled the widget will not visible after the app is deployed. By default, it's enabled (set to `{{true}}`). |
| Width | This property sets the width of the column. |
| Min width | This property sets the min width of the column. |
| Accent color | You can change the accent color of the column title by entering the Hex color code or choosing a color of your choice from the color picker. |
## Exposed variables
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/kanban/variables.png" alt="ToolJet - Widget Reference - Kanban widget" />
<img className="screenshot-full" src="/img/widgets/kanban/kanban2/exposedvar.png" alt="ToolJet - Widget Reference - Kanban widget" width="500"/>
</div>
| Variable | Description |
| -------- | ----------- |
| columns | The `columns` variable is an array of objects that includes the columns data in the respective objects. Since the columns variable is an array you'll need to specify the index of the object in the array to get the data within that object. Each object within a column has two keys - `id` and `title` and an array `cards` which is again an array of objects. Example: If you want to get the title of second card then you'll use `{{components.kanbanboard1.columns[1].title}}` - here we have specified the array index as `[1]` and then key which is the `title`. Similarly you can get the card details using `{{components.kanbanboard1.columns[0].cards[1].title}}` |
| lastAddedCard | The variable `lastAddedCard` holds the properties of the card that has been added lastly. It holds the following data - `id`, `title`, and `columnId` of the last added card. You can get the values using `{{components.kanbanboard1.lastAddedCard.title}}` |
| lastRemovedCard | The variable `lastRemovedCard` holds the properties of the card that has been recently deleted from the kanban. It holds the following data - `id`, `title`, and `columnId` of the recently deleted card. You can get the values using `{{components.kanbanboard1.lastRemovedCard.title}}` |
| lastCardMovement | The variable `lastCardMovement` holds the properties of the card that has been recently moved from its original position. It holds the following data - `originColumnId`, `destinationColumnId`, `originCardIndex`, `destinationCardIndex` and an object `cardDetails` which includes `title`. You can get the values using `{{components.kanbanboard1.lastCardMovement.cardDetails.title}}` or `{{components.kanbanboard1.lastCardMovement.destinationCardIndex}}` |
| lastUpdatedCard | The variable `lastUpdatedCard` holds `id`, `title`, and `columnId` of the latest modified card. You can get the values using `{{components.kanbanboard1.lastUpdatedCard.columnId}}` |
| selectedCard | The variable `selectedCard` holds `id`, `title`, `columnId`, and `description` of the selected card in the kanban. You can get the values using `{{components.kanbanboard1.selectedCard.description}}` |
| updatedCardData | The `updatedCardData` variable will hold the latest values of all the cards in the kanban. This variable won't have any values initially, it will have values only when any action on any of the card is performed like when the card is moved, added, deleted, or updated. |
| lastAddedCard | The variable `lastAddedCard` holds the values of the the last added card. It holds the following data - `id`, `title`, `description` and `columnId` of the last added card. You can get the values using `{{components.kanban1.lastAddedCard.title}}` |
| lastRemovedCard | The variable `lastRemovedCard` holds the properties of the card that has been recently deleted from the kanban. It holds the following data - `id`, `title`, `description` and `columnId` of the recently deleted card. You can get the values using `{{components.kanbanboard1.lastRemovedCard.title}}` |
| lastCardMovement | The variable `lastCardMovement` holds the properties of the card that has been recently moved from its original position. It holds the following data - `originColumnId`, `destinationColumnId`, `originCardIndex`, `destinationCardIndex` and an object `cardDetails` which includes `id`, `title`, `description` and `columnId` of the moved card. You can get the values using `{{components.kanbanboard1.lastCardMovement.cardDetails.title}}` or `{{components.kanbanboard1.lastCardMovement.destinationCardIndex}}` |
| lastSelectedCard | The variable `lastSelectedCard` holds the `id`, `title`, `columnId`, and `description` of the last selected(clicked to view) card on the kanban. You can get the values using `{{components.kanban1.lastSelectedCard.columnId}}` |
| lastUpdatedCard | The variable `lastUpdatedCard` holds the `id`, `title`, `description`, and `columnId` of the last updated card(using componenet specific action). You can get the values using `{{components.kanban1.lastUpdatedCard.columnId}}` |
| lastCardUpdate | The variable `lastCardUpdate` holds the old an new values of the property that has been changed in the card(using componenet specific action). You can get the values using `{{components.kanban1.lastCardUpdate[0].title.oldValue}}` |

View file

@ -38,7 +38,7 @@ Check [Action Reference](/docs/category/actions-reference) docs to get the detai
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/list-view/properties.png" alt="ToolJet - List view widget" />
<img className="screenshot-full" src="/img/widgets/list-view/props2.png" alt="ToolJet - List view widget" width="300"/>
</div>
@ -47,6 +47,8 @@ Check [Action Reference](/docs/category/actions-reference) docs to get the detai
| List data | Enter the data that you want to display into the widget. Data in the form of an array of objects or data from a query that returns an array of objects.| `{{ [ {id: 0, name: ABC, email: abc@bla.com}, {id: 1, name: XYZ, email: xyz@bla.com} ] }}` or `{{queries.xyz.data}}` |
| Row height | Enter a numerical value to set the row height accordingly. | Any number between `1` to `100` |
| Show bottom border | This property allows you to show or hide the row bottom border. | By default its `{{true}}`, set `{{false}}` to hide the border |
| Enable pagination | Enable it to set the number of rows per page. | Pagination is disabled by default. You can programmatically set to `{{true}}` to show a particular number of rows per page. |
### General
#### Tooltip

View file

@ -28,7 +28,7 @@ The table component will **auto-generate all the columns** as soon as the expect
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/table/columns.png" alt="ToolJet - Widget Reference - Table" width="400" />
<img className="screenshot-full" src="/img/widgets/table/columns2.png" alt="ToolJet - Widget Reference - Table" width="400" />
</div>
@ -78,6 +78,10 @@ To display email column, the key for the column should be `user.email`.
### Saving data
Enable `editable` property of a column to make the cells editable. If a data type is not selected, `string` is selected as the data type.
:::tip
You can programatically **enable**/**disable** the make **editable** field in the columns property by clicking on the **Fx** button.
:::
If the data in a cell is changed, `changeSet` property of the table object will have the index of the row and the field that changed.
For example, if the name field of second row of example in the 'Displaying Data' section is changed, `changeSet` will look like this:
@ -128,7 +132,7 @@ If the condition is true, the validation passes, otherwise return a string that
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/table/action.png" alt="ToolJet - Widget Reference - Table" width="400" />
<img className="screenshot-full" src="/img/widgets/table/action2.png" alt="ToolJet - Widget Reference - Table" width="800" />
</div>
@ -136,8 +140,11 @@ Action buttons will be displayed as the last column of the table. The styles of
| Property | Description |
| -------- | ------------ |
| Button text | Set the text that you want to be displayed on the action button. |
| Button position | Set the button position to the left or right |
| Background color (Action Button) | Background color of the action button. |
| Text color (Action Button) | Color of button-text of the action button. |
| Disable Action Button | Toggle on to disable the action button. You can programmatically set its value by clicking on the `Fx` button next to it, if set to `{{true}}`, the action button will be disabled and becomes non-functional. By default, its value is set to `{{false}}`. |
## Options

View file

@ -127,7 +127,7 @@ module.exports = {
// Please change this to your repo.
editUrl: 'https://github.com/ToolJet/Tooljet/blob/develop/docs/',
includeCurrentVersion: false,
lastVersion: '2.1.0',
lastVersion: '2.2.0',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),

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