Merge pull request #6232 from ToolJet/release/v2.5.0

Release/v2.5.0
This commit is contained in:
Akshay 2023-05-03 13:39:37 +05:30 committed by GitHub
commit 5a9f372a50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
745 changed files with 44800 additions and 5678 deletions

View file

@ -32,12 +32,12 @@ PGRST_HOST=
PGRST_JWT_SECRET=
# Checks every 24 hours to see if a new version of ToolJet is available
# (Enabled by default. Set 0 to disable)
CHECK_FOR_UPDATES=0
# (Enabled by default. Set false to disable)
CHECK_FOR_UPDATES=true
# Checks every 24 hours to update app telemetry data to ToolJet hub.
# (Telemetry is enabled by default. Set value to true to disable.)
# DISABLE_APP_TELEMETRY=false
# DISABLE_TOOLJET_TELEMETRY=false
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

View file

@ -1 +1,2 @@
node_modules/**
cypress-tests/**

View file

@ -36,15 +36,15 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js 18.3.0
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 18.3.0
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
@ -66,15 +66,15 @@ jobs:
github.ref == 'refs/heads/develop'
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js 18.3.0
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 18.3.0
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
@ -106,9 +106,9 @@ jobs:
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
@ -144,9 +144,9 @@ jobs:
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:

View file

@ -14,10 +14,10 @@ jobs:
Run-Cypress:
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'run-cypress' }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
@ -33,6 +33,12 @@ jobs:
sleep 1
done
- name: Getting cypress secrets
# use quotes around the secret, as its value
# is simply inserted as a string into the command
run: |
echo '${{ secrets.CYPRESS_SECRETS }}' > cypress.env.json
- name: Cypress integration test
uses: cypress-io/github-action@v5
with:
@ -41,7 +47,7 @@ jobs:
config-file: cypress-run.config.js
- name: Capturing screenshots
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: always()
with:
name: screenshots

View file

@ -17,7 +17,7 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setting tag
if: "${{ github.event.inputs.version != '' }}"
@ -28,7 +28,7 @@ jobs:
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

View file

@ -1 +1 @@
2.4.9
2.5.0

5
SECURITY.md Normal file
View file

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
If you notice a security vulnerability, please let the team know by sending an email to `security@tooljet.com`.

View file

@ -1,6 +1,11 @@
const { defineConfig } = require("cypress");
const { rmdir } = require("fs");
const fs = require("fs");
const XLSX = require("node-xlsx");
const pg = require("pg");
const path = require("path");
const pdf = require("pdf-parse");
module.exports = defineConfig({
execTimeout: 1800000,
@ -65,12 +70,19 @@ module.exports = defineConfig({
return require("./cypress/plugins/index.js")(on, config);
},
downloadsFolder: "cypress/downloads",
experimentalRunAllSpecs: true,
experimentalModfyObstructiveThirdPartyCode: true,
experimentalRunAllSpecs: true,
baseUrl: "http://localhost:8082",
specPattern: [
"cypress/e2e/editor/widget/*.cy.js"],
"cypress/e2e/editor/app-version/version.cy.js",
"cypress/e2e/exportImport/export.cy.js",
"cypress/e2e/exportImport/import.cy.js",
"cypress/e2e/database/database.cy.js",
"cypress/e2e/editor/widget/*.cy.js",
"cypress/e2e/editor/multipage/*.cy.js",
],
numTestsKeptInMemory: 1,
redirectionLimit: 10,
experimentalRunAllSpecs: true,

View file

@ -13,7 +13,7 @@ module.exports = defineConfig({
requestTimeout: 10000,
pageLoadTimeout: 20000,
responseTimeout: 10000,
viewportWidth: 1200,
viewportWidth: 1440,
viewportHeight: 960,
chromeWebSecurity: false,
trashAssetsBeforeRuns: true,
@ -50,13 +50,17 @@ module.exports = defineConfig({
on("task", {
deleteFolder(folderName) {
return new Promise((resolve, reject) => {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) {
console.error(err);
return reject(err);
}
resolve(null);
});
if (fs.existsSync(folderName)) {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) {
console.error(err);
return reject(err);
}
return resolve(null);
});
} else {
return resolve(null);
}
});
},
});
@ -73,7 +77,11 @@ module.exports = defineConfig({
experimentalModfyObstructiveThirdPartyCode: true,
experimentalRunAllSpecs: true,
baseUrl: "http://localhost:8082",
specPattern: "cypress/e2e/**/*.cy.js",
specPattern: [
"cypress/e2e/editor/widget/*.cy.js",
"cypress/e2e/multipage/*.cy.js",
],
downloadsFolder: "cypress/downloads",
numTestsKeptInMemory: 25,
redirectionLimit: 10,
experimentalRunAllSpecs: true,

View file

@ -5,13 +5,14 @@ export const cyParamName = (paramName = "") => {
export const commonSelectors = {
toastMessage: ".go3958317564",
oldToastMessage: ".go318386747",
newToastMessage:
'.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551',
toastCloseButton: '[data-cy="toast-close-button"]',
editButton: "[data-cy=edit-button]",
searchField: "[data-cy=widget-search-box]",
firstWidget: "[data-cy=widget-list]:eq(0)",
canvas: "[data-cy=real-canvas]",
appCardOptionsButton: "[data-cy=app-card-menu-icon]",
folderCardOptions: "[data-cy=folder-card-menu-icon]",
autoSave: "[data-cy=autosave-indicator]",
skipButton: ".driver-close-btn",
skipInstallationModal: "[data-cy=skip-button]",
@ -27,7 +28,7 @@ export const commonSelectors = {
loginButton: '[data-cy="login-button"]',
dropdown: "[data-cy=workspace-dropdown]",
backButton: "[data-cy=left-sidebar-back-button]",
emptyAppCreateButton: "[data-cy=create-new-application]",
dashboardAppCreateButton: '[data-cy="button-new-app-from-scratch"]',
appCreateButton: "[data-cy=create-new-app-button]",
createButton: "[data-cy=create-button]",
appNameInput: "[data-cy=app-name-input]",
@ -42,7 +43,7 @@ export const commonSelectors = {
createNewFolderButton: "[data-cy=create-new-folder-button]",
folderNameInput: "[data-cy=folder-name-input]",
createFolderButton: "[data-cy=create-folder-button]",
folderList: ".css-2kg7t4-MenuList",
folderList: ".css-169zxdi-MenuList",
empytyFolderImage: "[data-cy=empty-folder-image]",
emptyFolderText: "[data-cy=empty-folder-text]",
allApplicationsLink: "[data-cy=all-applications-link]",
@ -56,9 +57,7 @@ export const commonSelectors = {
viewerPageLogo: '[data-cy="viewer-page-logo"]',
lastPageArrow: '[data-cy="last-page-link"]',
nextPageArrow: '[data-cy="next-page-link"]',
emailFilterInput: '[data-cy="email-filter-input-field"]',
firstNameFilterInput: '[data-cy="first-name-filter-input-field"]',
lastNameFilterInput: '[data-cy="last-name-filter-input-field"]',
inputUserSearch: '[data-cy="input-field-user-filter-search"]',
filterButton: '[data-cy="filter-button"]',
copyIcon: '[data-cy="copy-icon"]',
addWorkspaceButton: '[data-cy="add-new-workspace-link"]',
@ -84,11 +83,11 @@ export const commonSelectors = {
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"]',
manageGroupsOption: '[data-cy="manage-groups-option"]',
manageSSOOption: '[data-cy="manage-sso-option"]',
workspaceVariableOption: '[data-cy="workspace-variable-option"]',
workspaceSettingsIcon: '[data-cy="icon-workspace-settings"]',
manageUsersOption: '[data-cy="users-list-item"]',
manageGroupsOption: '[data-cy="groups-list-item"]',
manageSSOOption: '[data-cy="sso-list-item"]',
workspaceVariableOption: '[data-cy="workspace-variables-list-item"]',
clearFilterButton: '[data-cy="clear-filter-button"]',
userStatusSelect: '[data-cy="user-status-select-continer"]',
emailInputLabel: '[data-cy="email-input-label"]',
@ -98,7 +97,7 @@ export const commonSelectors = {
enableToggleLabel: '[data-cy="enable-toggle-label"]',
enableToggle: '[data-cy="enable-toggle"]',
mainWrapper: '[data-cy="main-wrapper"]',
workspaceEditButton: '[data-cy="edit-workspace-button"]',
editRectangleIcon: '[data-cy="edit-rectangle-icon"]',
dashboardIcon: '[data-cy="dashboard-icon"]',
notificationsIcon: '[data-cy="notifications-icon"]',
notificationsCard: '[data-cy="notifications-card"]',
@ -159,6 +158,14 @@ export const commonSelectors = {
resetPasswordButton: '[data-cy="reset-password-button"]',
resetPasswordPageDescription: '[data-cy="reset-password-page-description"]',
backToLoginButton: '[data-cy="back-to-login-button"]',
breadcrumbTitle: '[data-cy="breadcrumb-title"]',
breadcrumbPageTitle: '[data-cy="breadcrumb-page-title"]',
labelFullNameInput: '[data-cy="label-full-name-input-field"]',
inputFieldFullName: '[data-cy="input-field-full-name"]',
labelEmailInput: '[data-cy="label-email-input-field"]',
inputFieldEmailAddress: '[data-cy="input-field-email"]',
closeButton: '[data-cy="close-button"]',
emptyAppCreateButton: "[data-cy='button-new-app-from-scratch']",
onboardingRadioButton: (radioButtonText) => {
return `[data-cy="${cyParamName(radioButtonText)}-radio-button"]`;
@ -194,6 +201,11 @@ export const commonSelectors = {
buttonSelector: (buttonText) => {
return `[data-cy="${cyParamName(buttonText)}-button"]`;
},
folderCardOptions: (folderName)=>{
return `[data-cy="${cyParamName(folderName)}-list-card"]>[data-cy="folder-card-menu-icon"]>svg`
},
};
export const commonWidgetSelector = {

View file

@ -5,7 +5,7 @@ export const dashboardSelector = {
emptyPageHeader: "[data-cy=empty-homepage-welcome-header]",
emptyPageDescription: "[data-cy=empty-homepage-description]",
createAppButton: "[data-cy=create-new-application]",
importAppButton: "[data-cy=import-an-application]",
importAppButton: '[data-cy="button-import-an-app"]',
chooseFromTemplate: "[data-cy=choose-from-template]",
modeToggle: '[data-cy="mode-switch-button"]',
dropdownText: "[data-cy=dropdown-organization-list]>>:eq(0)",
@ -22,7 +22,7 @@ export const dashboardSelector = {
changeButton: "[data-cy=change-button]",
addToFolderTitle: "[data-cy=add-to-folder-title]",
moveAppText: "[data-cy=move-selected-app-to-text]",
selectFolder: '[data-cy="select-folder"]>>>.css-s59k37-ValueContainer',
selectFolder: '[data-cy="select-folder"]>>>>.css-h380uj-Input',
addToFolderButton: "[data-cy=add-to-folder-button]",
appTemplateRow: '[data-cy="app-template-row"]',
homePageContent: '[data-cy="home-page-content"]',
@ -30,6 +30,8 @@ export const dashboardSelector = {
folderLabel: '[data-cy="folder-info"]',
dashboardAppsHeaderLabel:'[data-cy="app-header-label"]',
versionLabel: '[data-cy="version-label"]',
dashboardAppCreateButton: '[data-cy="button-new-app-from-scratch"]',
appCardIcon: (iconName) => {
return `[data-cy="app-card-${cyParamName(iconName)}-icon"]`;
},

View file

@ -10,12 +10,12 @@ export const appVersionSelectors = {
createNewVersion: '[data-cy="create-new-version-title"]',
versionNamelabel: '[data-cy="version-name-label"]',
appVersionMenuField:
'[data-cy="app-version-selector"] .custom-version-selector__indicators',
'[data-cy="app-version-selector"] .undefined__indicators',
versionNameInputField: '[data-cy="version-name-input-field"]',
createVersionFromLabel: '[data-cy="create-version-from-label"]',
createVersionInputField: '[data-cy="create-version-from-input-field"]',
createNewVersionButton: '[data-cy="create-new-version-button"]',
appVersionContentList: ".custom-version-selector__menu-list",
appVersionContentList: ".undefined__menu-list",
};
export const exportAppModalSelectors = {
selectVersionTitle: '[data-cy= "select-a-version-to-export-title"]',

View file

@ -16,8 +16,8 @@ export const groupsSelector = {
permissionsLink: "[data-cy=permissions-link]",
searchBox: '[data-cy="select-search"]',
appSearchBox:
"[data-cy=select-search] >>.css-1e1a1lx-control > .css-s59k37-ValueContainer",
searchBoxOptions: ".css-2kg7t4-MenuList",
"[data-cy=select-search]>>>>>.dropdown-heading-value > .gray",
searchBoxOptions: ".panel-content",
appAddButton: "[data-cy=add-button]",
addButton: '[data-cy="add-button"]',
nameTableHeader: ".active [data-cy=name-header]",
@ -50,14 +50,10 @@ export const groupsSelector = {
deleteGroupLink: (groupname) => {
return `[data-cy="${cyParamName(groupname)}-group-delete-link"]`;
},
selectAddButton: (groupname) => {
return `[data-cy="${cyParamName(
groupname
)}-group-select-search-add-button"]`;
},
mutiSelectAddButton: (groupname) => {
return `[data-cy="${cyParamName(
groupname
)}-group-multi-select-search-add-button"]`;
},
selectAddButton: '[data-cy="add-button"]'
};

View file

@ -1,7 +1,7 @@
export const ssoSelector = {
pagetitle: "[data-cy=manage-sso-page-title]",
generalSettingsElements: {
generalSettings: "[data-cy=left-menu-items] :eq(0)",
generalSettings: '[data-cy="general-settings-list-item"]',
enableSignupLabel: '[data-cy="enable-sign-up-label"]',
helperText: "[data-cy=enable-sign-up-helper-text]",
allowDefaultSSOLabel: '[data-cy="allow-default-sso-label"]',
@ -19,7 +19,7 @@ export const ssoSelector = {
workspaceLoginUrl: '[data-cy="workspace-login-url"]',
cancelButton: "[data-cy=cancel-button]",
saveButton: "[data-cy=save-button]",
google: "[data-cy=left-menu-items] :eq(1)",
google: '[data-cy="google-list-item"]',
googleEnableToggle: '[data-cy="google-enable-toggle"]',
statusLabel: "[data-cy=status-label]",
clientIdLabel: "[data-cy=client-id-label]",
@ -29,8 +29,9 @@ export const ssoSelector = {
googleTile: '[data-cy="google-sign-in-text"]',
googleIcon: "[data-cy=google-sso-icon]",
googleSSOText: "[data-cy=google-sso-text]",
git: "[data-cy=left-menu-items] :eq(2)",
gitEnableToggle: '[data-cy="git-enable-toogle"]',
git: '[data-cy="github-list-item"]',
gitEnableToggle: '[data-cy="github-toggle-input"]',
githubLabel:'[data-cy="github-toggle-label"]',
clientSecretLabel: "[data-cy=client-secret-label]",
encriptedLabel: "[data-cy=encripted-label]",
clientSecretInput: "[data-cy=client-secret-input]",
@ -48,4 +49,7 @@ export const ssoSelector = {
signInHeader: '[data-cy="sign-in-header"]',
workspaceSubHeader: '[data-cy="workspace-sign-in-sub-header"]',
noLoginMethodWarning: '[data-cy="no-login-methods-warning"]',
passwordLoginToggleLbale: '[data-cy="label-password-login"]',
alertText: '[data-cy="alert-text"]',
disablePasswordHelperText: '[data-cy="disable-password-helper-text"]',
};

View file

@ -1,27 +1,31 @@
export const usersSelector = {
dropdown: "[data-cy=workspace-dropdown]",
inviteUserButton: "[data-cy=invite-new-user]",
buttonAddUsers: "[data-cy=button-invite-new-user]",
usersElements: {
pageTitle: "[data-cy=users-page-title]",
nameTitile: "[data-cy=name-title]",
emailTitle: "[data-cy=email-title]",
statusTitle: "[data-cy=status-title]",
usersTableNameColumnHeader: '[data-cy="users-table-name-column-header"]',
usersTableEmailColumnHeader: '[data-cy="users-table-email-column-header"]',
usersTableStatusColumnHeader: '[data-cy="users-table-status-column-header"]',
usersFilterLabel: '[data-cy="users-filter-label"]'
},
usersPageTitle: '[data-cy="title-users-page"]',
userFilterInput: '[data-cy="users-filter-input"]',
adminUserName: "[data-cy=user-name]",
adminUserEmail: "[data-cy=user-email]",
userStatus: "[data-cy=user-status]:eq(0)",
userState: "[data-cy=user-state]:eq(0)",
userStatus: "[data-cy=user-status]",
cardTitle: "[data-cy=add-new-user]",
firstNameInput: "[data-cy=first-name-input]",
addUsersCardTitle: '[data-cy="add-users-card-title"]',
inputFieldName: "[data-cy=first-name-input]",
lastNameInput: "[data-cy=last-name-input]",
emailLabel: "[data-cy=email-label]",
emailInput: "[data-cy=email-input]",
cancelButton: "[data-cy=cancel-button]",
createUserButton: "[data-cy=create-user-button]",
fisrtNameError: "[data-cy=first-name-error]",
lastNameError: "[data-cy=last-name-error]",
emailError: "[data-cy=email-error]",
buttonInviteUsers: '[data-cy="button-invite-users"]',
buttonInviteWithEmail: '[data-cy="button-invite-with-email"]',
buttonUploadCsvFile: '[data-cy="button-upload-csv-file"]',
fullNameError: '[data-cy="error-message-fullname"]',
emailError: '[data-cy="error-message-email"]',
pageLogo: "[data-cy=page-logo]",
invitePageHeader: '[data-cy="invite-page-header"]',
invitePgaeSubHeader: '[data-cy="invite-page-sub-header"]',
@ -47,7 +51,12 @@ export const usersSelector = {
inviteBulkUserButton: '[data-cy="invite-bulk-user-button"]',
bulkUserUploadPageTitle: '[data-cy="bulk-user-upload-page-title"]',
bulkUSerUploadInput: '[data-cy="bulk-user-upload-input"]',
downloadTemplateButton: '[data-cy="download-template-button"]',
createUsersButton: '[data-cy="create-users-button"]',
buttonDownloadTemplate: '[data-cy="button-download-template"]',
buttonUploadUsers: '[data-cy="button-upload-users"]',
helperTextBulkUpload: '[data-cy="helper-text-bulk-upload"]',
iconBulkUpload:'[data-cy="icon-bulk-upload"]',
helperTextSelectFile:'[data-cy="helper-text-select-file"]',
helperTextDropFile: '[data-cy="helper-text-drop-file"]',
inputFieldBulkUpload: '[data-cy="input-field-bulk-upload"]',
copyInvitationLink: '[data-cy="copy-invitation-link"]',
};

View file

@ -12,7 +12,7 @@ export const tableSelector = {
labelNumberOfRecords: '[data-cy="footer-number-of-records"]',
buttonDownloadDropdown: '[data-tip="Download"]',
buttonDownloadDropdown: '[data-tooltip-id="tooltip-for-download"]',
optionDownloadCSV: '[data-cy="option-download-CSV"]',
optionDownloadExcel: '[data-cy="option-download-execel"]',
optionDownloadPdf: '[data-cy="option-download-pdf"]',
@ -31,7 +31,7 @@ export const tableSelector = {
return `[data-cy="column-header-${column}"]`;
},
filterButton: '[data-tip="Filter data"]',
filterButton: '[data-tooltip-id="tooltip-for-filter-data"]',
headerFilters: '[data-cy="header-filters"]',
labelNoFilters: '[data-cy="label-no-filters"]',
buttonAddFilter: '[data-cy="button-add-filter"]',

View file

@ -123,6 +123,14 @@ export const commonText = {
backToLoginButton: "Back to log in",
resetPasswordPageDescription:
"Your password has been reset successfully, log into ToolJet to continue your session",
labelFullNameInput: "Enter full name",
labelEmailInput: "Email address",
breadcrumbworkspaceSettingTitle:"Workspace settings",
breadcrumbGlobalDatasourceTitle: "Global datasources",
breadcrumbDatabaseTitle: "Databse",
breadcrumbApplications: "Applications",
breadcrumbSettings: "Settings",
emailPageDescription: (email) => {
return `Weve sent an email to ${email} with a verification link. Please use that to verify your email address.`;
},

View file

@ -1,12 +1,12 @@
export const ssoText = {
pagetitle: "Manage SSO",
pagetitle: " SSO",
generalSettingsElements: {
generalSettings: "General Settings",
enableSignupLabel: "Enable Signup",
helperText: "New account will be created for user's first time SSO sign in",
allowDefaultSSOLabel: "Allow default SSO",
allowDefaultSSOHelperText:
"Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO.",
"Allow users to authenticate via default SSO. Default SSO configurations can be overridden by \n workspace level SSO.",
allowedDomainLabel: "Allowed domains",
allowedDomainHelperText:
"Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com",
@ -14,7 +14,7 @@ export const ssoText = {
workspaceLoginHelpText: "Use this URL to login directly to this workspace",
},
cancelButton: "Cancel",
saveButton: "Save",
saveButton: "Save changes",
allowedDomain: "tooljet.io,gmail.com",
ssoToast: "updated sso configurations",
ssoToast2: "updated SSO configurations",
@ -50,4 +50,7 @@ export const ssoText = {
googleSignUpText: "Sign up with Google",
gitSignUpText: "Sign up with GitHub",
gitUserStatusToast:"GitHub login failed - User does not exist in the workspace",
passwordLoginToggleLbale: 'Password login ',
alertText: 'Danger zone',
disablePasswordHelperText: 'Disable password login only if your SSO is configured otherwise you will get logged out.',
};

View file

@ -1,20 +1,21 @@
export const usersText = {
usersElements: {
pageTitle: "Users & Permissions",
nameTitile: "NAME",
emailTitle: "EMAIL",
statusTitle: "STATUS",
usersTableNameColumnHeader: "NAME",
usersTableEmailColumnHeader: "EMAIL",
usersTableStatusColumnHeader: "STATUS",
usersFilterLabel: "Showing"
},
usersPageTitle: "users",
breadcrumbUsersPageTitle: " Users & permissions",
adminUserName: "The Developer",
adminUserEmail: "dev@tooljet.io",
adminUserState: "Archive",
inviteUserButton: "Invite new user",
cardTitle: "Add new user",
buttonAddUsers: "Add users",
addUsersCardTitle: "Add users",
emailLabel: "Email address",
cancelButton: "Cancel",
createUserButton: "Create User",
fieldRequired: "This field is required",
buttonInviteUsers: "Invite users",
errorTextFieldRequired: "This field is required",
exsitingEmail: "User with such email already exists.",
userCreatedToast: "User has been created",
inviteCopiedToast: "Invitation URL copied",
@ -47,6 +48,13 @@ export const usersText = {
"Added to the workspace and password has been set successfully.",
inviteBulkUserButton:"Invite bulk users",
bulkUserUploadPageTitle: "Upload Users",
downloadTemplateButton: 'Download Template',
createUsersButton:"Create Users",
buttonDownloadTemplate: 'Download Template',
buttonUploadUsers:"Upload users",
buttonInviteWithEmail: " Invite with email",
buttonUploadCsvFile: "Upload CSV file",
helperTextBulkUpload: 'Download the ToolJet template to add user details or format your file in the same as the template. ToolJet wont be able to recognise files in any other format. ',
helperTextSelectFile:'Select a CSV file to upload',
helperTextDropFile: 'Or drag and drop it here',
};

View file

@ -1,30 +1,30 @@
export const multipageText={
labelPages: "Pages",
pageNameHome: "Home",
headerPageHandle: "Page Handle",
pageHandleModalTitle: "Edit page handle",
editPagehandleInfo: "Changing the page handle will break any existing apps that are using this page.",
pageHanmdleEmptyMessage: "Page handle cannot be empty",
samePagehandleToast: "Page with same handle already exists",
export const multipageText = {
labelPages: "Pages",
pageNameHome: "Home",
headerPageHandle: "Page Handle",
pageHandleModalTitle: "Edit page handle",
editPagehandleInfo:
"Changing the page handle will break any existing apps that are using this page.",
pageHanmdleEmptyMessage: "Page handle cannot be empty",
samePagehandleToast: "Page with same handle already exists",
optionRename: "Rename",
optionMarkHome: "Mark home",
optionHidePage: "Hide page",
optionRename: "Rename",
optionMarkHome: "Mark home",
optionHidePage: "Hide page",
optionEventHandler: "Event Handlers",
eventModalTitle: "Page Events",
labelEvents: "Events",
addEventHandlerLink: "+ Add event handler",
noEventHandlerInfo: "This page doesn't have any event handlers",
optionEventHandler: "Event Handlers",
eventModalTitle: "Page Events",
labelEvents: "Events",
addEventHandlerLink: "+ Add event handler",
noEventHandlerInfo: "This query doesn't have any event handlers",
optionDeletePage: "Delete Page",
deleteModalMessage: "Do you really want to delete Home page?",
optionDeletePage: "Delete Page",
deleteModalMessage: "Do you really want to delete this page?",
headerSettings: "Settings",
optionDisableMenu: "Disable Menu",
disableMenuDescription:
"To hide the page navigation sidebar in viewer mode, set this option to on.",
headerSettings: "Settings",
optionDisableMenu: "Disable Menu",
disableMenuDescription: "To hide the page navigation sidebar in viewer mode, set this option to on.",
labelNoPagesFound: "No pages found",
}
labelNoPagesFound: "No pages found",
};

View file

@ -13,7 +13,7 @@ describe("Password reset functionality", () => {
before(() => {
cy.appUILogin();
addNewUserMW(data.firstName, data.lastName, data.email);
addNewUserMW(data.firstName, data.email);
logout();
});

View file

@ -22,7 +22,9 @@ describe("User signup", () => {
cy.visit("/");
});
it("Verify sign up page elements", () => {
cy.get(commonSelectors.createAnAccountLink).click();
cy.wait(500);
cy.reload();
cy.get(commonSelectors.createAnAccountLink).realClick();
SignUpPageElements();
cy.clearAndType(commonSelectors.nameInputField, data.fullName);

View file

@ -27,7 +27,7 @@ describe("dashboard", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.folderName = `${fake.companyName}-Folder`;
data.cloneAppName = `${data.appName}-Clone`;
data.cloneAppName = `cloned-${data.appName}`;
data.updatedFolderName = `New-${data.folderName}`;
beforeEach(() => {
@ -40,9 +40,10 @@ describe("dashboard", () => {
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=", {
fixture: "intercept/emptyDashboard.json",
}).as("emptyDashboard");
cy.intercept("GET", "/api/folders?searchKey=",{"folders":[]}).as("folders");
login();
cy.wait("@emptyDashboard");
cy.wait("@folders");
deleteDownloadsFolder();
});
@ -51,19 +52,17 @@ describe("dashboard", () => {
cy.get(
commonSelectors.workspaceName
).verifyVisibleElement("have.text", "My workspace");
cy.get(commonSelectors.workspaceEditButton).should("be.visible");
cy.get(commonSelectors.workspaceName).click();
cy.get(commonSelectors.editRectangleIcon).should("be.visible");
cy.get(commonSelectors.appCreateButton).verifyVisibleElement(
"have.text",
"New app"
"Create new app"
);
cy.get(dashboardSelector.folderLabel).should("be.visible");
cy.get(dashboardSelector.folderLabel).should(($el) => {
expect($el.contents().first().text().trim()).to.eq("Folders");
});
cy.get(commonSelectors.createNewFolderButton).verifyVisibleElement(
"have.text",
"+ Create new"
);
cy.get(commonSelectors.createNewFolderButton).should("be.visible");
cy.get(commonSelectors.allApplicationLink).verifyVisibleElement(
"have.text",
commonText.allApplicationLink
@ -92,32 +91,34 @@ describe("dashboard", () => {
commonText.viewReadNotifications
);
cy.get(commonSelectors.profileSettings).should("be.visible").click();
cy.get(profileSelector.profileLink).verifyVisibleElement(
"have.text",
profileText.profileLink
);
cy.get(dashboardSelector.modeToggle)
.verifyVisibleElement("have.text", dashboardText.darkModeText)
cy.get(dashboardSelector.modeToggle).should("be.visible")
.click();
cy.get(commonSelectors.mainWrapper)
.should("have.attr", "class")
.and("contain", "theme-dark");
cy.get(dashboardSelector.modeToggle)
.verifyVisibleElement("have.text", dashboardText.lightModeText)
.click();
cy.get(dashboardSelector.modeToggle).click();
cy.get(dashboardSelector.homePageContent)
.should("have.attr", "class")
.and("contain", "bg-light-gray");
cy.get(commonSelectors.profileSettings).should("be.visible").click();
cy.get(profileSelector.profileLink).verifyVisibleElement(
"have.text",
profileText.profileLink
);
cy.get(commonSelectors.logoutLink).verifyVisibleElement(
"have.text",
commonText.logoutLink
);
cy.get(dashboardSelector.dashboardAppsHeaderLabel).verifyVisibleElement(
"have.text",
dashboardText.dashboardAppsHeaderLabel
);
cy.get(commonSelectors.breadcrumbTitle).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(
commonText.breadcrumbApplications
);
});
cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement( "have.text",dashboardText.dashboardAppsHeaderLabel);
cy.get(dashboardSelector.versionLabel).should("be.visible");
cy.get(dashboardSelector.emptyPageImage).should("be.visible");
cy.get(dashboardSelector.emptyPageHeader).verifyVisibleElement(
@ -128,17 +129,15 @@ describe("dashboard", () => {
"have.text",
dashboardText.emptyPageDescription
);
cy.get(dashboardSelector.createAppButton).verifyVisibleElement(
cy.get(dashboardSelector.dashboardAppCreateButton).verifyVisibleElement(
"have.text",
dashboardText.createAppButton
);
cy.get(dashboardSelector.importAppButton)
.invoke("text")
.then((text) => {
expect(text.replace(/\u00a0/g, " ")).equal(
dashboardText.importAppButton
);
});
// cy.get(dashboardSelector.importAppButton).verifyVisibleElement(
// "have.text",
// dashboardText.importAppButton
// );
cy.get(dashboardSelector.appTemplateRow).should("be.visible");
});
@ -166,9 +165,8 @@ describe("dashboard", () => {
});
});
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.changeIconOption)
viewAppCardOptions(data.appName);
cy.get(commonSelectors.appCardOptions(commonText.changeIconOption)
).verifyVisibleElement("have.text", commonText.changeIconOption);
cy.get(
commonSelectors.appCardOptions(commonText.addToFolderOption)
@ -226,7 +224,7 @@ describe("dashboard", () => {
cancelModal(commonText.cancelButton);
cy.get(commonSelectors.appCardOptionsButton).click();
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.removeFromFolderOption)
).click();
@ -256,7 +254,7 @@ describe("dashboard", () => {
cy.get(commonSelectors.editorPageLogo).click();
cy.wait("@appLibrary");
cy.wait(500);
cy.reloadAppForTheElement(data.appName);
cy.reloadAppForTheElement(data.cloneAppName);
cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
@ -304,10 +302,12 @@ describe("dashboard", () => {
});
it("Should verify the app CRUD operation", () => {
data.appName = `${fake.companyName}-App`;
cy.appUILogin();
cy.createApp();
cy.renameApp(data.appName);
cy.get(commonSelectors.editorPageLogo).click();
cy.reloadAppForTheElement(data.appName);
cy.get(commonSelectors.appCard(data.appName)).should(
"contain.text",
data.appName
@ -328,6 +328,7 @@ describe("dashboard", () => {
});
it("Should verify the folder CRUD operation", () => {
data.appName = `${fake.companyName}-App`;
cy.appUILogin();
cy.createApp();
cy.renameApp(data.appName);

View file

@ -4,6 +4,8 @@ import * as common from "Support/utils/common";
import { ssoText } from "Texts/manageSSO";
import * as SSO from "Support/utils/manageSSO";
import { commonSelectors } from "Selectors/common";
import { commonText } from "Texts/common";
describe("Manage SSO for multi workspace", () => {
const data = {};
@ -12,10 +14,14 @@ describe("Manage SSO for multi workspace", () => {
});
it("Should verify General settings page elements", () => {
common.navigateToManageSSO();
cy.get(ssoSelector.pagetitle).verifyVisibleElement(
"have.text",
"SSO"
);
cy.get(commonSelectors.breadcrumbTitle).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(
commonText.breadcrumbworkspaceSettingTitle
);
});
cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement( "have.text",ssoText.pagetitle);
cy.get(ssoSelector.cardTitle).verifyVisibleElement(
"have.text",
ssoText.generalSettingsElements.generalSettings
@ -43,16 +49,20 @@ describe("Manage SSO for multi workspace", () => {
);
SSO.generalSettings();
cy.get(ssoSelector.alertText).verifyVisibleElement( "have.text",ssoText.alertText);
cy.get(ssoSelector.passwordEnableToggle).should("be.visible");
cy.get(ssoSelector.passwordLoginToggleLbale).verifyVisibleElement( "have.text",ssoText.passwordLoginToggleLbale);
cy.get(ssoSelector.disablePasswordHelperText).verifyVisibleElement( "have.text",ssoText.disablePasswordHelperText);
SSO.passwordPageElements();
});
it("Should verify Google SSO page elements", () => {
common.navigateToManageSSO();
cy.get(ssoSelector.google).should("be.visible").click();
cy.get(ssoSelector.cardTitle)
.should(($el) => {
expect($el.contents().first().text().trim()).to.eq(ssoText.googleTitle);
})
.and("be.visible");
cy.get(ssoSelector.cardTitle).verifyVisibleElement(
"have.text",ssoText.googleTitle)
cy.get(ssoSelector.googleEnableToggle).should("be.visible");
cy.get(ssoSelector.clientIdLabel).verifyVisibleElement(
"have.text",
@ -84,12 +94,10 @@ describe("Manage SSO for multi workspace", () => {
common.navigateToManageSSO();
cy.get(ssoSelector.git).should("be.visible").click();
cy.get(ssoSelector.cardTitle)
.should(($el) => {
expect($el.contents().first().text().trim()).to.eq(ssoText.gitTitle);
})
.and("be.visible");
cy.get(ssoSelector.hostNameLabel).verifyVisibleElement(
cy.get(ssoSelector.githubLabel).verifyVisibleElement(
"have.text",ssoText.gitTitle);
cy.get(ssoSelector.hostNameLabel).verifyVisibleElement(
"have.text",
ssoText.hostNameLabel
);
@ -135,23 +143,6 @@ describe("Manage SSO for multi workspace", () => {
);
});
it("Should verify Password login page elements", () => {
common.navigateToManageSSO();
cy.get(ssoSelector.password).should("be.visible").click();
cy.get(ssoSelector.cardTitle)
.should(($el) => {
expect($el.contents().first().text().trim()).to.eq(
ssoText.passwordTitle
);
})
.and("be.visible");
cy.get(ssoSelector.passwordEnableToggle).should("be.visible");
SSO.passwordPageElements();
});
it("Should verify the workspace login page", () => {
data.workspaceName = fake.companyName;
@ -220,13 +211,9 @@ describe("Manage SSO for multi workspace", () => {
SSO.workspaceLogin(data.workspaceName);
common.navigateToManageSSO();
cy.get(ssoSelector.password).should("be.visible").click();
cy.get(ssoSelector.passwordEnableToggle).uncheck();
cy.get(commonSelectors.buttonSelector("Yes")).click();
cy.get(ssoSelector.statusLabel).verifyVisibleElement(
"have.text",
ssoText.disabledLabel
);
SSO.visitWorkspaceLoginPage();
cy.get(ssoSelector.googleSSOText).verifyVisibleElement(
"have.text",
@ -245,7 +232,6 @@ describe("Manage SSO for multi workspace", () => {
common.createWorkspace(data.workspaceName);
cy.wait(300);
SSO.disableDefaultSSO();
cy.get(ssoSelector.password).should("be.visible").click();
cy.get(ssoSelector.passwordEnableToggle).uncheck();
cy.get(commonSelectors.buttonSelector("Yes")).click();
SSO.visitWorkspaceLoginPage();

View file

@ -1,11 +1,11 @@
import { commonSelectors } from "Selectors/common";
import { commonSelectors } from "Selectors/common"
import { fake } from "Fixtures/fake";
import { usersText } from "Texts/manageUsers"
import { usersSelector } from "Selectors/manageUsers";
import { usersText } from "Texts/manageUsers";
import * as users from "Support/utils/manageUsers";
import * as common from "Support/utils/common";
import { path } from "Texts/common";
import { dashboardSelector } from "../../../constants/selectors/dashboard";
import { dashboardSelector } from "Selectors/dashboard";
const data = {};
data.firstName = fake.firstName;
@ -21,106 +21,43 @@ describe("Manage Users for multiple workspace", () => {
common.navigateToManageUsers();
users.manageUsersElements();
cy.get(usersSelector.cancelButton).click();
cy.get(usersSelector.usersElements.nameTitile).should("be.visible");
cy.get(usersSelector.inviteUserButton).click();
cy.get(commonSelectors.cancelButton).click();
cy.get(usersSelector.usersPageTitle).should("be.visible");
cy.get(usersSelector.buttonAddUsers).click();
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).verifyVisibleElement(
cy.get(usersSelector.buttonInviteUsers).click();
cy.get(usersSelector.fullNameError).verifyVisibleElement("have.text",usersText.errorTextFieldRequired);
cy.get(usersSelector.emailError).verifyVisibleElement("have.text",usersText.errorTextFieldRequired);
cy.clearAndType(commonSelectors.inputFieldFullName, data.firstName);
cy.get(commonSelectors.inputFieldEmailAddress).clear();
cy.get(usersSelector.buttonInviteUsers).click();
cy.get(usersSelector.emailError).verifyVisibleElement("have.text",usersText.errorTextFieldRequired);
cy.get(commonSelectors.inputFieldFullName).clear();
cy.clearAndType(commonSelectors.inputFieldEmailAddress, data.email);
cy.get(usersSelector.buttonInviteUsers).click();
cy.get(usersSelector.fullNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.lastNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.emailError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
usersText.errorTextFieldRequired
);
cy.clearAndType(usersSelector.firstNameInput, data.firstName);
cy.get(usersSelector.lastNameInput).clear();
cy.get(usersSelector.emailInput).clear();
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.lastNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.emailError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.firstNameInput).clear();
cy.get(usersSelector.emailInput).clear();
cy.clearAndType(usersSelector.lastNameInput, data.lastName);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.emailError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.firstNameInput).clear();
cy.get(usersSelector.lastNameInput).clear();
cy.clearAndType(usersSelector.emailInput, data.email);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.lastNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.firstNameInput).clear();
cy.clearAndType(usersSelector.lastNameInput, data.lastName);
cy.clearAndType(usersSelector.emailInput, data.email);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.lastNameInput).clear();
cy.clearAndType(usersSelector.firstNameInput, data.firstName);
cy.clearAndType(usersSelector.emailInput, data.email);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.lastNameError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.get(usersSelector.emailInput).clear();
cy.clearAndType(usersSelector.firstNameInput, data.firstName);
cy.clearAndType(usersSelector.lastNameInput, data.lastName);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.emailError).verifyVisibleElement(
"have.text",
usersText.fieldRequired
);
cy.clearAndType(usersSelector.firstNameInput, data.firstName);
cy.clearAndType(usersSelector.lastNameInput, data.lastName);
cy.clearAndType(commonSelectors.inputFieldFullName, data.firstName);
cy.clearAndType(
usersSelector.emailInput,
commonSelectors.inputFieldEmailAddress,
usersText.adminUserEmail
);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.buttonInviteUsers).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonSelectors.newToastMessage,
usersText.exsitingEmail
);
});
it("Should verify the confirm invite page and new user account", () => {
common.navigateToManageUsers();
users.inviteUser(data.firstName, data.lastName, data.email);
users.inviteUser(data.firstName, data.email);
users.confirmInviteElements();
cy.clearAndType(commonSelectors.passwordInputField, "pass");
@ -200,7 +137,7 @@ describe("Manage Users for multiple workspace", () => {
cy.contains("td", data.email)
.parent()
.within(() => {
cy.get("td img").click();
cy.get(usersSelector.copyInvitationLink).click();
});
cy.verifyToastMessage(
commonSelectors.toastMessage,
@ -219,7 +156,6 @@ describe("Manage Users for multiple workspace", () => {
cy.get("@copyToClipboardPrompt").then((prompt) => {
common.logout();
cy.visit(prompt.args[0][1]);
cy.url().should("include", path.confirmInvite);
});
cy.get(usersSelector.acceptInvite).click();

View file

@ -8,8 +8,8 @@ import { groupsText } from "Texts/manageGroups";
import * as permissions from "Support/utils/userPermissions";
import { usersSelector } from "Selectors/manageUsers";
import { commonText } from "Texts/common";
import { workspaceVarSelectors } from "../../../constants/selectors/workspaceVariable";
import { workspaceVarText } from "../../../constants/texts/workspacevarText";
import { workspaceVarSelectors } from "Selectors/workspaceVariable";
import { workspaceVarText } from "Texts/workspacevarText";
const data = {};
data.firstName = fake.firstName;
@ -29,20 +29,18 @@ describe("User permissions", () => {
permissions.reset();
permissions.addNewUserMW(
data.firstName,
data.lastName,
data.email,
data.email
);
cy.get("body").then(($title) => {
if ($title.text().includes(dashboardText.emptyPageDescription)) {
cy.get(commonSelectors.emptyAppCreateButton).click();
cy.get(commonSelectors.dashboardAppCreateButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
usersText.createAppPermissionToast
);
} else {
cy.contains(dashboardText.createAppButton).should("not.exist");
cy.log("The app is created by the admin");
}
});
common.logout();
@ -65,7 +63,7 @@ describe("User permissions", () => {
common.navigateToManageGroups();
cy.get(groupsSelector.appSearchBox).click();
cy.get(groupsSelector.searchBoxOptions).contains(data.appName).click();
cy.get(groupsSelector.selectAddButton("all_users")).click();
cy.get(groupsSelector.selectAddButton).click();
cy.get("table").contains("td", data.appName);
cy.contains("td", data.appName)
.parent()
@ -84,8 +82,9 @@ describe("User permissions", () => {
.parent()
.within(() => {
cy.get(commonSelectors.appTitle(data.appName)).trigger("mouseover");
cy.get(commonSelectors.launchButton).should("have.class", "tj-disabled-btn");
});
cy.get(commonSelectors.launchButton).should("exist").and("be.disabled");
permissions.adminLogin();
cy.contains("tr", data.appName)
@ -105,7 +104,7 @@ describe("User permissions", () => {
.within(() => {
cy.get(commonSelectors.appTitle(data.appName)).trigger("mouseover");
});
cy.get(commonSelectors.launchButton).should("exist").and("be.disabled");
cy.get(commonSelectors.launchButton).should("have.class", "tj-disabled-btn");
cy.get(commonSelectors.editButton).should("exist").and("be.enabled");
cy.get(commonSelectors.workspaceName).click();
@ -114,6 +113,7 @@ describe("User permissions", () => {
cy.get(commonSelectors.workspaceName).click();
cy.contains("My workspace").should("be.visible").click();
cy.wait(200);
});
it("Should verify the Create and Delete app permission", () => {
@ -163,8 +163,6 @@ describe("User permissions", () => {
common.logout();
cy.login(data.email, usersText.password);
cy.contains("+ Create new").should("exist");
cy.get(commonSelectors.createNewFolderButton).click();
cy.clearAndType(commonSelectors.folderNameInput, data.folderName);
cy.get(commonSelectors.createFolderButton).click();
@ -182,13 +180,12 @@ describe("User permissions", () => {
common.logout();
cy.login(data.email, usersText.password);
cy.contains("+ Create new").should("not.exist");
permissions.adminLogin();
cy.contains("td", data.appName)
.parent()
.within(() => {
cy.get("td a").contains("Delete").click();
cy.get("td a").contains("Remove").click();
});
common.logout();

View file

@ -11,7 +11,7 @@ describe("Manage SSO for single workspace", () => {
});
it("Should verify General settings page elements", () => {
common.navigateToManageSSO();
cy.get(ssoSelector.pagetitle).verifyVisibleElement("have.text", "SSO");
// cy.get(ssoSelector.pagetitle).verifyVisibleElement("have.text", "SSO");
cy.get(ssoSelector.cardTitle).verifyVisibleElement(
"have.text",
ssoText.generalSettingsElements.generalSettings

View file

@ -11,7 +11,7 @@ import { buttonText } from "Texts/button";
import { verifyComponent, deleteComponentAndVerify } from "Support/utils/basicComponents";
describe("App Export Functionality", () => {
describe("App Version Functionality", () => {
var data = {};
data.appName = `${fake.companyName}-App`;
let currentVersion = "";
@ -73,12 +73,10 @@ describe("App Export Functionality", () => {
deleteVersionAndVerify(currentVersion = "v5", deleteVersionText.deleteToastMessage(currentVersion = "v5"));
cy.reload();
releasedVersionAndVerify(currentVersion = "v3")
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");

View file

@ -72,6 +72,7 @@ describe("Multipage", () => {
cy.get(multipageSelector.pageMenuIcon).should("be.visible");
cy.get(multipageSelector.pageMenuIcon).click();
cy.wait(500);
// cy.get(multipageSelector.pageEventHandler).click({force:true})
cy.get(multipageSelector.pageEventHandler).verifyVisibleElement(
"have.text",
@ -133,6 +134,7 @@ describe("Multipage", () => {
.verifyVisibleElement("have.text", "test_page")
.click();
cy.get(multipageSelector.pageMenuIcon).click();
cy.wait(500);
cy.get(multipageSelector.pageHandleText).verifyVisibleElement(
"have.text",
"test-page"
@ -154,6 +156,7 @@ describe("Multipage", () => {
cy.get(multipageSelector.homePageLabel).click();
cy.get(multipageSelector.pageMenuIcon).click();
cy.wait(500);
cy.get(multipageSelector.deletePageOptionButton).click();
cy.get(".modal-title").verifyVisibleElement(
"have.text",
@ -173,11 +176,13 @@ describe("Multipage", () => {
cy.get('[data-cy="pages-name-test_page"]').should("be.visible");
cy.get(multipageSelector.pageMenuIcon).click();
cy.wait(500);
cy.get(multipageSelector.deletePageOptionButton).click();
cy.get(multipageSelector.modalConfirmButton).click();
cy.notVisible(multipageSelector.homePageLabel);
cy.get(multipageSelector.pageMenuIcon).click();
cy.wait(500);
cy.get(multipageSelector.eventHandlerOptionButton).click();
cy.get(multipageSelector.modalTitlePageEvents).verifyVisibleElement(
"have.text",
@ -216,6 +221,7 @@ describe("Multipage", () => {
addNewPage("test");
cy.get(multipageSelector.pageMenuIcon).click();
cy.wait(500);
cy.get(multipageSelector.pageHandleText).click();
cy.get(multipageSelector.modalTitleEditPageHandle).verifyVisibleElement(
"have.text",
@ -249,21 +255,21 @@ describe("Multipage", () => {
multipageText.samePagehandleToast
);
});
it.only("should verify the basic bunctions of multipage", () => {
it("should verify the basic functions of multipage", () => {
cy.get(multipageSelector.sidebarPageButton).click();
cy.get(multipageSelector.pagesPinIcon).click();
addNewPage('pageOne');
addNewPage('pageTwo')
addNewPage('pageThree')
addNewPage("pageOne");
addNewPage("pageTwo");
addNewPage("pageThree");
hideOrUnhidePage('pageOne')
hideOrUnhidePage('pageTwo')
hideOrUnhidePage('pageOne', 'unhide')
addEventHandler('pageThree')
hideOrUnhidePage("pageOne");
hideOrUnhidePage("pageTwo");
hideOrUnhidePage("pageOne", "unhide");
addEventHandler("pageThree");
cy.get(multipageSelector.closeModal).click();
setHomePage('pageThree')
modifyPageHandle('home', '1')
cy.waitForAutoSave()
setHomePage("pageThree");
modifyPageHandle("home", "1");
cy.waitForAutoSave();
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(multipageSelector.homePageLabel).click();

View file

@ -5,7 +5,7 @@ import { tableSelector } from "Selectors/table";
import {
verifyComponent,
deleteComponentAndVerify,
verifyComponentWithOutLabel
verifyComponentWithOutLabel,
} from "Support/utils/basicComponents";
import {
openAccordion,
@ -33,29 +33,39 @@ import {
describe("Basic components", () => {
const data = {};
beforeEach(() => {
data.appName = `${fake.companyName}-App`;
data.appName = `${fake.companyName}-${fake.companyName}-App`;
cy.appUILogin();
cy.createApp();
cy.modifyCanvasSize(900, 900);
cy.get('[data-tooltip-id="tooltip-for-hide-query-editor"]').click();
cy.renameApp(data.appName);
cy.intercept("GET", "/api/comments/*").as("loadComments");
});
it("Should verify Toggle switch", () => {
cy.dragAndDropWidget("Toggle Switch", 50, 50);
verifyComponent("toggleswitch1");
cy.resizeWidget("toggleswitch1", 850, 600);
cy.resizeWidget("toggleswitch1", 650, 400);
openEditorSidebar("toggleswitch1");
editAndVerifyWidgetName("toggleswitch2");
verifyAndModifyParameter(commonWidgetText.parameterLabel, "label");
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.get(
'[data-cy="draggable-widget-toggleswitch2"] > .form-check-label'
).should("have.text", "label");
cy.openInCurrentTab(commonWidgetSelector.previewButton);
verifyComponent("toggleswitch2");
cy.get(
'[data-cy="draggable-widget-toggleswitch2"] > .form-check-label'
).should("have.text", "label");
cy.go("back");
cy.wait("@appVersion");
deleteComponentAndVerify("toggleswitch2");
cy.get(commonSelectors.editorPageLogo).click();
@ -64,11 +74,11 @@ describe("Basic components", () => {
it("Should verify Checkbox", () => {
cy.dragAndDropWidget("Checkbox", 50, 50);
cy.resizeWidget("checkbox1", 100, 200);
// cy.resizeWidget("checkbox1", 50, 200);
cy.forceClickOnCanvas();
verifyComponent("checkbox1");
cy.resizeWidget("checkbox1", 850, 600);
cy.resizeWidget("checkbox1", 650, 400);
openEditorSidebar("checkbox1");
editAndVerifyWidgetName("checkbox2");
@ -97,7 +107,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("radiobutton1");
cy.resizeWidget("radiobutton1", 850, 600);
cy.resizeWidget("radiobutton1", 650, 400);
openEditorSidebar("radiobutton1");
editAndVerifyWidgetName("radiobutton2");
@ -125,7 +135,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("dropdown1");
cy.resizeWidget("dropdown1", 850, 600);
cy.resizeWidget("dropdown1", 650, 400);
openEditorSidebar("dropdown1");
editAndVerifyWidgetName("dropdown2");
@ -148,14 +158,14 @@ describe("Basic components", () => {
cy.deleteApp(data.appName);
});
//pending
it("Should verify Rating", () => {
cy.dragAndDropWidget("Rating", 300, 300);
it.skip("Should verify Rating", () => {
cy.dragAndDropWidget("Rating", 200, 200);
cy.get('[data-cy="draggable-widget-starrating1"]').click({ force: true });
// cy.resizeWidget("starrating1", 300, 500);
cy.resizeWidget("starrating1", 200, 500);
cy.forceClickOnCanvas();
verifyComponent("starrating1");
cy.resizeWidget("starrating1", 850, 600);
cy.resizeWidget("starrating1", 650, 400);
openEditorSidebar("starrating1");
editAndVerifyWidgetName("starrating2");
@ -183,7 +193,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("buttongroup1");
cy.resizeWidget("buttongroup1", 850, 600);
cy.resizeWidget("buttongroup1", 650, 400);
openEditorSidebar("buttongroup1");
editAndVerifyWidgetName("buttongroup2");
@ -207,12 +217,11 @@ describe("Basic components", () => {
it("Should verify Calendar", () => {
cy.dragAndDropWidget("Calendar", 50, 50);
cy.get('[data-tip="Hide query editor"]').click();
cy.get('[data-cy="draggable-widget-calendar1"]').click({ force: true });
cy.forceClickOnCanvas();
verifyComponent("calendar1");
cy.resizeWidget("calendar1", 850, 600);
cy.resizeWidget("calendar1", 650, 400);
openEditorSidebar("calendar1");
editAndVerifyWidgetName("calendar2");
@ -234,7 +243,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("chart1");
cy.resizeWidget("chart1", 850, 600);
cy.resizeWidget("chart1", 650, 400);
openEditorSidebar("chart1");
editAndVerifyWidgetName("chart2", ["Chart data", "Properties"]);
@ -262,7 +271,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("circularprogressbar1");
cy.resizeWidget("circularprogressbar1", 850, 600);
cy.resizeWidget("circularprogressbar1", 650, 400);
openEditorSidebar("circularprogressbar1");
editAndVerifyWidgetName("circularprogressbar2");
@ -286,7 +295,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("codeeditor1");
cy.resizeWidget("codeeditor1", 850, 600);
cy.resizeWidget("codeeditor1", 650, 400);
openEditorSidebar("codeeditor1");
editAndVerifyWidgetName("codeeditor2");
@ -309,7 +318,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("colorpicker1");
cy.resizeWidget("colorpicker1", 850, 600);
cy.resizeWidget("colorpicker1", 650, 400);
openEditorSidebar("colorpicker1");
editAndVerifyWidgetName("colorpicker2");
@ -326,25 +335,30 @@ describe("Basic components", () => {
cy.deleteApp(data.appName);
});
//needed fix
//needed fix
it.skip("Should verify Custom Component", () => {
cy.dragAndDropWidget("Custom Component", 50, 50);
cy.get('[data-cy="draggable-widget-customcomponent1"]').click({ force: true });
cy.get('[data-cy="draggable-widget-customcomponent1"]').click({
force: true,
});
cy.forceClickOnCanvas();
verifyComponent("customcomponent1");
openEditorSidebar("customcomponent1");
// editAndVerifyWidgetName("customcomponent2", ["Code"]);
closeAccordions(["Code"]);
cy.get(commonWidgetSelector.WidgetNameInputField).type("{selectAll}{backspace}customcomponent2", {delay:30});
cy.forceClickOnCanvas()
cy.get(commonWidgetSelector.WidgetNameInputField).type(
"{selectAll}{backspace}customcomponent2",
{ delay: 30 }
);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(name)).trigger("mouseover");
cy.get(commonWidgetSelector.widgetConfigHandle(name))
.click()
.should("have.text", name);
cy.resizeWidget("customcomponent1", 850, 600);
cy.resizeWidget("customcomponent1", 650, 400);
openEditorSidebar("customcomponent1");
cy.forceClickOnCanvas();
@ -365,7 +379,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("container1");
cy.resizeWidget("container1", 850, 600);
cy.resizeWidget("container1", 650, 400);
openEditorSidebar("container1");
editAndVerifyWidgetName("container2", ["Layout"]);
@ -389,7 +403,7 @@ describe("Basic components", () => {
cy.forceClickOnCanvas();
verifyComponent("daterangepicker1");
cy.resizeWidget("daterangepicker1", 850, 600);
cy.resizeWidget("daterangepicker1", 650, 400);
openEditorSidebar("daterangepicker1");
editAndVerifyWidgetName("daterangepicker2");
@ -406,20 +420,30 @@ describe("Basic components", () => {
cy.deleteApp(data.appName);
});
//visible issue
//visible issue
it.skip("Should verify Divider", () => {
verifyComponentWithOutLabel("Divider", "divider1", "divider2", data.appName)
verifyComponentWithOutLabel(
"Divider",
"divider1",
"divider2",
data.appName
);
});
it("Should verify File Picker", () => {
verifyComponentWithOutLabel("File Picker", "filepicker1", "filepicker2", data.appName)
verifyComponentWithOutLabel(
"File Picker",
"filepicker1",
"filepicker2",
data.appName
);
});
it("Should verify Form", () => {
cy.dragAndDropWidget("Form", 50, 50);
verifyComponent("form1");
cy.resizeWidget("form1", 850, 600);
cy.resizeWidget("form1", 650, 400);
openEditorSidebar("form1");
editAndVerifyWidgetName("form2");
@ -440,7 +464,7 @@ describe("Basic components", () => {
cy.dragAndDropWidget("HTML Viewe", 50, 50, "HTML Viewer"); // search logic WIP
verifyComponent("html1");
cy.resizeWidget("html1", 850, 600);
cy.resizeWidget("html1", 650, 400);
openEditorSidebar("html1");
editAndVerifyWidgetName("html2");
@ -458,31 +482,32 @@ describe("Basic components", () => {
});
it("Should verify Icon", () => {
verifyComponentWithOutLabel("Icon", "icon1", "icon2", data.appName)
verifyComponentWithOutLabel("Icon", "icon1", "icon2", data.appName);
});
it("Should verify Iframe", () => {
verifyComponentWithOutLabel("Iframe", "iframe1", "iframe2", data.appName)
verifyComponentWithOutLabel("Iframe", "iframe1", "iframe2", data.appName);
});
it("Should verify Kamban", () => {
verifyComponentWithOutLabel("Kanban Board", "kanbanboard1", "kanbanboard2", data.appName) });
it.skip("Should verify Kamban", () => {
verifyComponentWithOutLabel("Kanban", "kanban1", "kanban2", data.appName);
});
it("Should verify Link", () => {
verifyComponentWithOutLabel("Link", "link1", "link2", data.appName)
verifyComponentWithOutLabel("Link", "link1", "link2", data.appName);
});
it("Should verify Map", () => {
cy.dragAndDropWidget("Map", 50, 50);
cy.get("body").then($body => {
cy.get("body").then(($body) => {
if ($body.find(".dismissButton").length > 0) {
cy.get('.dismissButton').click();
cy.get(".dismissButton").click();
}
})
});
verifyComponent("map1");
cy.resizeWidget("map1", 850, 600);
cy.resizeWidget("map1", 650, 400);
openEditorSidebar("map1");
editAndVerifyWidgetName("map2");
@ -500,15 +525,14 @@ describe("Basic components", () => {
});
it("Should verify Modal", () => {
verifyComponentWithOutLabel("Modal", "modal1", "modal2", data.appName)
verifyComponentWithOutLabel("Modal", "modal1", "modal2", data.appName);
});
it("Should verify PDF", () => {
cy.dragAndDropWidget("PDF", 50, 50);
cy.get('[data-tip="Hide query editor"]').click();
verifyComponent("pdf1");
cy.resizeWidget("pdf1", 850, 600);
cy.resizeWidget("pdf1", 650, 400);
openEditorSidebar("pdf1");
editAndVerifyWidgetName("pdf2");
@ -526,35 +550,70 @@ describe("Basic components", () => {
});
it("Should verify Pagination", () => {
verifyComponentWithOutLabel("Pagination", "pagination1", "pagination2", data.appName)
verifyComponentWithOutLabel(
"Pagination",
"pagination1",
"pagination2",
data.appName
);
});
it("Should verify QR Scanner", () => {
verifyComponentWithOutLabel("QR Scanner", "qrscanner1", "qrscanner2", data.appName)
verifyComponentWithOutLabel(
"QR Scanner",
"qrscanner1",
"qrscanner2",
data.appName
);
});
it("Should verify Range Slider", () => {
verifyComponentWithOutLabel("Range Slider", "rangeslider1", "rangeslider2", data.appName)
it.skip("Should verify Range Slider", () => {
verifyComponentWithOutLabel(
"Range Slider",
"rangeslider1",
"rangeslider2",
data.appName
);
});
it("Should verify Rich Text Editor", () => {
verifyComponentWithOutLabel("Text Editor", "richtexteditor1", "richtexteditor2", data.appName)
verifyComponentWithOutLabel(
"Text Editor",
"richtexteditor1",
"richtexteditor2",
data.appName
);
});
it("Should verify Spinner", () => {
verifyComponentWithOutLabel("Spinner", "spinner1", "spinner2", data.appName);
verifyComponentWithOutLabel(
"Spinner",
"spinner1",
"spinner2",
data.appName
);
});
it("Should verify Statistics", () => {
verifyComponentWithOutLabel("Statistics", "statistics1", "statistics2", data.appName)
verifyComponentWithOutLabel(
"Statistics",
"statistics1",
"statistics2",
data.appName
);
});
it("Should verify Steps", () => {
verifyComponentWithOutLabel("Steps", "steps1", "steps2", data.appName)
verifyComponentWithOutLabel("Steps", "steps1", "steps2", data.appName);
});
it("Should verify SVG Image", () => {
verifyComponentWithOutLabel("SVG Image", "svgimage1", "svgimage2", data.appName)
verifyComponentWithOutLabel(
"SVG Image",
"svgimage1",
"svgimage2",
data.appName
);
});
it("Should verify Tabs", () => {
@ -562,7 +621,7 @@ describe("Basic components", () => {
verifyComponent("tabs1");
deleteComponentAndVerify("image1");
cy.resizeWidget("tabs1", 850, 600);
cy.resizeWidget("tabs1", 650, 400);
openEditorSidebar("tabs1");
editAndVerifyWidgetName("tabs2");
@ -576,29 +635,49 @@ describe("Basic components", () => {
deleteComponentAndVerify("tabs2");
cy.get(commonSelectors.editorPageLogo).click();
cy.deleteApp(data.appName);
cy.deleteApp(data.appName);
});
it("Should verify Tags", () => {
verifyComponentWithOutLabel("Tags", "tags1", "tags2", data.appName)
verifyComponentWithOutLabel("Tags", "tags1", "tags2", data.appName);
});
it("Should verify Textarea", () => {
verifyComponentWithOutLabel("Textarea", "textarea1", "textarea2", data.appName)
verifyComponentWithOutLabel(
"Textarea",
"textarea1",
"textarea2",
data.appName
);
});
it("Should verify Timeline", () => {
verifyComponentWithOutLabel("Timeline", "timeline1", "timeline2", data.appName)
verifyComponentWithOutLabel(
"Timeline",
"timeline1",
"timeline2",
data.appName
);
});
it("Should verify Timer", () => {
verifyComponentWithOutLabel("Timer", "timer1", "timer2", data.appName)
verifyComponentWithOutLabel("Timer", "timer1", "timer2", data.appName);
});
it("Should verify Tree Select", () => {
verifyComponentWithOutLabel("Tree Select", "treeselect1", "treeselect2", data.appName)
verifyComponentWithOutLabel(
"Tree Select",
"treeselect1",
"treeselect2",
data.appName
);
});
it("Should verify Vertical Divider", () => {
verifyComponentWithOutLabel("Vertical Divider", "verticaldivider1", "verticaldivider2", data.appName)
});
verifyComponentWithOutLabel(
"Vertical Divider",
"verticaldivider1",
"verticaldivider2",
data.appName
);
});
});

View file

@ -292,6 +292,7 @@ describe("Date Picker widget", () => {
addTextWidgetToVerifyValue(`components.${data.widgetName}.value`);
cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 160);
cy.waitForAutoSave()
cy.openInCurrentTab(commonWidgetSelector.previewButton);

View file

@ -80,10 +80,13 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.wait(2500);
cy.get(
`${commonWidgetSelector.draggableWidget(commonWidgetText.text1)}:eq(0)`
).click();
)
.realHover()
.realClick();
verifyAndModifyParameter("Text", codeMirrorInputLabel("listItem.name"));
cy.forceClickOnCanvas();
cy.get(
@ -123,10 +126,11 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.wait(2500);
cy.get(
`${commonWidgetSelector.draggableWidget(data.widgetName)}:eq(0)`
).click();
cy.get(`${commonWidgetSelector.draggableWidget(data.widgetName)}:eq(0)`)
.realHover()
.click("topRight", { force: true });
verifyAndModifyParameter(
listviewText.showBottomBorder,
@ -165,6 +169,7 @@ describe("List view widget", () => {
data.customMessage
);
cy.forceClickOnCanvas();
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionLayout);
verifyAndModifyToggleFx(
@ -275,7 +280,7 @@ describe("List view widget", () => {
deleteInnerWidget(data.widgetName, "button1");
deleteInnerWidget(data.widgetName, "image1");
dropWidgetToListview("Text Input", 250, 50, data.widgetName);
dropWidgetToListview("Text Input", 450, 20, data.widgetName);
cy.forceClickOnCanvas();
openEditorSidebar(data.widgetName);
@ -287,6 +292,7 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.wait(3500);
cy.get(
`${commonWidgetSelector.draggableWidget(commonWidgetText.text1)}:eq(0)`
@ -312,6 +318,7 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.wait(2500);
cy.get(
`${commonWidgetSelector.draggableWidget(data.widgetName)}:eq(0)`
@ -334,6 +341,7 @@ describe("List view widget", () => {
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
cy.wait(2500);
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionGenaral);
@ -409,8 +417,7 @@ describe("List view widget", () => {
cy.get(`[data-cy=${data.widgetName.toLowerCase()}-row-1]`)
.invoke("height")
.should("be.gte", 98)
.and("be.lte", 99);
.should("equal", 99);
cy.get(`[data-cy=${data.widgetName.toLowerCase()}-row-1]`)
.invoke("attr", "class")
.and("not.contain", "border-bottom");

View file

@ -0,0 +1,423 @@
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { buttonText } from "Texts/button";
import { fake } from "Fixtures/fake";
import { commonWidgetText } from "Texts/common";
import { verifyControlComponentAction } from "Support/utils/button";
import { selectEvent } from "Support/utils/events";
import {
launchModal,
closeModal,
launchButton,
verifySize,
addAndVerifyColor,
typeOnFx,
} from "Support/utils/modal";
import {
openAccordion,
verifyAndModifyParameter,
openEditorSidebar,
verifyAndModifyToggleFx,
addDefaultEventHandler,
addAndVerifyTooltip,
verifyComponentFromInspector,
verifyAndModifyStylePickerFx,
verifyWidgetColorCss,
selectColourFromColourPicker,
verifyLoaderColor,
fillBoxShadowParams,
verifyBoxShadowCss,
verifyLayout,
verifyTooltip,
editAndVerifyWidgetName,
verifyPropertiesGeneralAccordion,
verifyStylesGeneralAccordion,
} from "Support/utils/commonWidget";
describe("Modal", () => {
beforeEach(() => {
cy.appUILogin();
cy.createApp();
cy.dragAndDropWidget("Modal");
});
it("should verify the properties of the modal component", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.alertMessage = fake.randomSentence;
data.widgetName = fake.widgetName;
data.customTitle = fake.randomSentence;
data.tooltipText = fake.randomSentence;
data.buttonText = fake.companyName;
cy.renameApp(data.appName);
launchModal("modal1");
cy.get('[data-cy="modal-title"]').verifyVisibleElement(
"have.text",
"This title can be changed"
);
cy.get('[data-cy="modal-body"]').should("be.visible");
cy.get('[data-cy="modal-close-button"]').click();
cy.notVisible('[data-cy="modal-title"]');
openEditorSidebar("modal1", ["Options", "Properties", "Layout"]);
editAndVerifyWidgetName(data.widgetName, [
"Options",
"Properties",
"Layout",
]);
verifyComponentFromInspector(data.widgetName);
openAccordion(commonWidgetText.accordionProperties);
verifyAndModifyParameter("Title", data.customTitle);
launchModal(data.widgetName);
cy.get('[data-cy="modal-title"]').verifyVisibleElement(
"have.text",
data.customTitle
);
cy.get('[data-cy="modal-close-button"]').click();
verifyAndModifyToggleFx(
buttonText.loadingState,
commonWidgetText.codeMirrorLabelFalse
);
launchModal(data.widgetName);
cy.get(".spinner-border").should("be.visible");
cy.get(
commonWidgetSelector.parameterTogglebutton(buttonText.loadingState)
).click();
cy.notVisible(".spinner-border");
verifyAndModifyToggleFx(
"Hide title bar",
commonWidgetText.codeMirrorLabelFalse
);
cy.notVisible('[data-cy="modal-title"]');
cy.get('[data-cy="hide-title-bar-toggle-button"]').click();
cy.get('[data-cy="modal-title"]').verifyVisibleElement(
"have.text",
data.customTitle
);
cy.realPress("Escape");
cy.notVisible('[data-cy="modal-title"]');
verifyAndModifyToggleFx(
"Close on escape key",
commonWidgetText.codeMirrorLabelTrue
);
launchModal(data.widgetName);
cy.realPress("Escape");
cy.get('[data-cy="modal-title"]').verifyVisibleElement(
"have.text",
data.customTitle
);
closeModal(data.widgetName);
launchModal(data.widgetName);
verifySize("Medium");
verifySize("Large");
verifySize("Small");
verifyAndModifyToggleFx(
"Use default trigger button",
commonWidgetText.codeMirrorLabelTrue
);
cy.get('[data-cy="modal-close-button"]').click();
cy.notVisible(launchButton(data.widgetName));
cy.get('[data-cy="use-default-trigger-button-toggle-button"]').click();
cy.get(
'[data-cy="trigger-button-label-input-field"]'
).clearAndTypeOnCodeMirror(data.buttonText);
cy.forceClickOnCanvas();
cy.get(launchButton(data.widgetName))
.verifyVisibleElement("have.text", data.buttonText)
.click();
openAccordion(commonWidgetText.accordionEvents);
selectEvent("On open", "Show Alert");
cy.get('[data-cy="modal-close-button"]').click();
launchModal(data.widgetName);
cy.verifyToastMessage(commonSelectors.toastMessage, "Hello world!");
cy.get('[data-cy="modal-close-button"]').click();
verifyLayout(data.widgetName);
cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click();
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterShowOnDesktop
)
).click();
cy.get(commonWidgetSelector.widgetDocumentationLink).should(
"have.text",
"Modal documentation"
);
cy.get(commonSelectors.editorPageLogo).click();
cy.deleteApp(data.appName);
});
it("should verify the styles of the modal widget", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.boxShadowColor = fake.randomRgba;
data.colourHex = fake.randomRgbaHex;
data.boxShadowParam = fake.boxShadowParam;
data.backgroundColor = fake.randomRgba;
cy.renameApp(data.appName);
launchModal("modal1");
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
addAndVerifyColor(
"Header background color",
"#ffffffff",
data.backgroundColor,
"[data-cy='modal-header']"
);
data.backgroundColor = fake.randomRgba;
addAndVerifyColor(
"Header title color",
"#000000",
data.backgroundColor,
"[data-cy='modal-header']",
"color"
);
data.backgroundColor = fake.randomRgba;
addAndVerifyColor(
"Body background color",
"#ffffffff",
data.backgroundColor,
"[data-cy='modal-body']"
);
data.backgroundColor = fake.randomRgba;
addAndVerifyColor(
"Trigger button background color",
"#4D72FA",
data.backgroundColor,
launchButton("modal1"),
"background-color"
);
data.backgroundColor = fake.randomRgba;
addAndVerifyColor(
"Trigger button text color",
"#ffffffff",
data.backgroundColor,
launchButton("modal1"),
"color"
);
cy.get("[data-cy='modal-header']").realClick();
verifyAndModifyToggleFx(
commonWidgetText.parameterVisibility,
commonWidgetText.codeMirrorLabelTrue
);
cy.get('[data-cy="modal-close-button"]').click();
cy.get(commonWidgetSelector.draggableWidget("modal1")).should(
"not.be.visible"
);
cy.get(commonWidgetSelector.parameterTogglebutton("Visibility")).click();
verifyAndModifyToggleFx(
commonWidgetText.parameterDisable,
commonWidgetText.codeMirrorLabelFalse
);
cy.waitForAutoSave();
cy.get(launchButton("modal1")).should("have.attr", "disabled");
cy.get(commonWidgetSelector.parameterTogglebutton("Disable")).click();
launchModal("modal1");
cy.get('[data-cy="modal-title"]').verifyVisibleElement(
"have.text",
"This title can be changed"
);
cy.get(commonSelectors.editorPageLogo).click();
cy.deleteApp(data.appName);
});
it("should verify the app preview", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.bgColor = fake.randomRgba;
data.titleColor = fake.randomRgba;
data.bodyColor = fake.randomRgba;
data.buttonColor = fake.randomRgba;
data.buttonTextColor = fake.randomRgba;
data.customTitle = fake.randomSentence;
cy.get(".close-svg > path").click();
cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 50);
cy.get(".close-svg > path").click();
cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 100);
cy.get(".close-svg > path").click();
cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 150);
cy.get(".close-svg > path").click();
cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 200);
cy.get(".close-svg > path").click();
cy.dragAndDropWidget(commonWidgetText.toggleSwitch, 600, 250);
cy.get(".close-svg > path").click();
cy.renameApp(data.appName);
launchModal("modal1");
verifyAndModifyParameter("Title", data.customTitle);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
addAndVerifyColor(
"Header background color",
"#ffffffff",
data.bgColor,
"[data-cy='modal-header']"
);
addAndVerifyColor(
"Header title color",
"#000000",
data.titleColor,
"[data-cy='modal-header']",
"color"
);
addAndVerifyColor(
"Body background color",
"#ffffffff",
data.bodyColor,
"[data-cy='modal-body']"
);
addAndVerifyColor(
"Trigger button background color",
"#4D72FA",
data.buttonColor,
launchButton("modal1"),
"background-color"
);
addAndVerifyColor(
"Trigger button text color",
"#ffffffff",
data.buttonTextColor,
launchButton("modal1"),
"color"
);
closeModal("modal1");
launchModal("modal1");
typeOnFx(
commonWidgetText.parameterVisibility,
"{{components.toggleswitch1.value"
);
cy.get("[data-cy='modal-header']").realClick();
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
typeOnFx(
commonWidgetText.parameterDisable,
"{{components.toggleswitch2.value"
);
cy.get('[data-cy="sidebar-option-properties"]').click();
typeOnFx("Loading State", "{{components.toggleswitch3.value");
cy.get("[data-cy='modal-header']").realClick();
typeOnFx("Hide title bar", "{{components.toggleswitch4.value");
cy.get("[data-cy='modal-header']").realClick();
typeOnFx("Hide close button", "{{components.toggleswitch5.value");
cy.get("[data-cy='modal-header']").realClick();
cy.waitForAutoSave();
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(2000);
cy.notVisible(launchButton("modal1"));
cy.get(commonWidgetSelector.draggableWidget("toggleswitch1"))
.find(".form-check-input")
.click();
cy.get(launchButton("modal1")).should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("toggleswitch2"))
.find(".form-check-input")
.click();
cy.get(launchButton("modal1")).should("have.attr", "disabled");
cy.get(commonWidgetSelector.draggableWidget("toggleswitch2"))
.find(".form-check-input")
.click();
cy.get(commonWidgetSelector.draggableWidget("toggleswitch3"))
.find(".form-check-input")
.click();
launchModal("modal1");
cy.get(".spinner-border").should("be.visible");
cy.realPress("Escape");
cy.get(commonWidgetSelector.draggableWidget("toggleswitch3"))
.find(".form-check-input")
.click();
cy.get(commonWidgetSelector.draggableWidget("toggleswitch4"))
.find(".form-check-input")
.click();
launchModal("modal1");
cy.notVisible('[data-cy="modal-title"]');
cy.realPress("Escape");
cy.get(commonWidgetSelector.draggableWidget("toggleswitch4"))
.find(".form-check-input")
.click();
launchModal("modal1");
verifyWidgetColorCss(
"[data-cy='modal-header']",
"background-color",
data.bgColor,
true
);
verifyWidgetColorCss(
"[data-cy='modal-header']",
"color",
data.titleColor,
true
);
verifyWidgetColorCss(
"[data-cy='modal-body']",
"background-color",
data.bodyColor,
true
);
cy.realPress("Escape");
verifyWidgetColorCss(
launchButton("modal1"),
"color",
data.buttonTextColor,
true
);
verifyWidgetColorCss(
launchButton("modal1"),
"background-color",
data.buttonColor,
true
);
launchModal("modal1");
cy.get('[data-cy="modal-title"]').verifyVisibleElement(
"have.text",
data.customTitle
);
cy.realPress("Escape");
cy.get(commonWidgetSelector.draggableWidget("toggleswitch5"))
.find(".form-check-input")
.click();
launchModal("modal1");
cy.notVisible('[data-cy="modal-close-button"]');
});
});

View file

@ -123,8 +123,7 @@ describe("Multiselect widget", () => {
cy.get(multiselectSelector.dropdownAllItems)
.first()
.should("have.text", multiselectText.dropdwonOptionSelectAll)
.click()
.click();
.realClick();
verifyMultiselectHeader(
data.widgetName,

View file

@ -239,10 +239,7 @@ describe("Number Input", () => {
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.reload();
openEditorSidebar(numberInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();

View file

@ -139,7 +139,7 @@ describe("Table", () => {
);
cy.get(tableSelector.buttonCloseFilters).should("be.visible");
cy.get(tableSelector.buttonAddFilter).dblclick();
cy.get(tableSelector.buttonAddFilter).realClick().realClick();
cy.get(tableSelector.labelColumn).verifyVisibleElement(
"have.text",
@ -211,7 +211,8 @@ describe("Table", () => {
codeMirrorInputLabel(`[{id:1,name:"Mike",email:"mike@example.com" },{id:2,name:"Nina",email:"nina@example.com" },{id:3,name:"Steph",email:"steph@example.com" },{id:4,name:"Oliver",email:"oliver@example.com" },
]`)
);
cy.get('[data-cy="inspector-close-icon"]').click();
// cy.get('[data-cy="inspector-close-icon"]').click();
cy.forceClickOnCanvas();
cy.waitForAutoSave();
verifyTableElements([
{ id: 1, name: "Mike", email: "mike@example.com" },
@ -271,10 +272,9 @@ describe("Table", () => {
cy.get('[data-cy="rightActions-cell-2"]')
.eq(0)
.should("have.text", "FakeName1");
selectDropdownOption(
`[data-cy="dropdown-action-button-position"] > .select-search`,
0
);
cy.get(`[data-cy="dropdown-action-button-position"]>>:eq(0)`).click();
cy.get('[data-index="0"] > .select-search-option').click();
cy.get('[data-cy="leftActions-cell-0"]')
.eq(0)
.should("have.text", "FakeName1");
@ -345,25 +345,30 @@ describe("Table", () => {
deleteAndVerifyColumn("name");
deleteAndVerifyColumn("email");
addAndOpenColumnOption("Fake-String", `string`);
selectDropdownOption('[data-cy="input-overflow"] > .select-search', `wrap`);
selectDropdownOption('[data-cy="input-overflow"] >>:eq(0)', `wrap`);
cy.get('[data-index="0"]>.select-search-option:eq(1)').realClick();
cy.wait(2000);
verifyAndEnterColumnOptionInput("key", "name");
verifyAndEnterColumnOptionInput("Text color", "red");
verifyAndEnterColumnOptionInput(
"Cell Background Color",
"{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}yellow"
"{backspace}{backspace}{backspace}{backspace}{backspace}yellow"
);
cy.get(
'[data-cy="input-and-label-cell-background-color"] > .form-label'
).click();
cy.wait(500);
cy.get(tableSelector.column(0))
.eq(0)
.should("have.css", "background-color", "rgb(255, 255, 0)")
.find("span")
.should("have.css", "color", "rgb(255, 0, 0)")
.should("have.css", "background-color", "rgb(255, 255, 0)", {
timeout: 10000,
})
.last()
.should("have.css", "color", "rgb(62, 82, 91)")
.and("have.text", "Sarah");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
cy.get('[data-cy="header-validation"]').verifyVisibleElement(
"have.text",
"Validation"
@ -383,7 +388,7 @@ describe("Table", () => {
addAndOpenColumnOption("fake-number", `number`);
verifyAndEnterColumnOptionInput("key", "id");
// verifyAndEnterColumnOptionInput("Cell Background Color", "black");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
cy.get('[data-cy="header-validation"]').verifyVisibleElement(
"have.text",
"Validation"
@ -391,16 +396,17 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("Min value", "2");
verifyAndEnterColumnOptionInput("Max value", "3");
addInputOnTable(0, 0, "2");
addInputOnTable(0, 0, "0");
verifyInvalidFeedback(0, 0, "Minimum value is 2");
verifyInvalidFeedback(0, 3, "Maximum value is 3");
openEditorSidebar(data.widgetName);
deleteAndVerifyColumn("fake-number");
openEditorSidebar(data.widgetName);
addAndOpenColumnOption("fake-text", `text`);
verifyAndEnterColumnOptionInput("key", "email");
// verifyAndEnterColumnOptionInput("Cell Background Color", "");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
verifySingleValueOnTable(0, 0, "sarah@example.com");
addInputOnTable(0, 0, "mike@example.com", "textarea");
openEditorSidebar(data.widgetName);
@ -413,7 +419,7 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("Labels", '{{["One","Two","Three"]');
// verifyAndEnterColumnOptionInput("Cell Background Color", "fakeString");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
selectDropdownOption(`${tableSelector.column(0)}:eq(0) .badge`, 1);
cy.get(`${tableSelector.column(0)}:eq(0) .badge`).should(
"have.text",
@ -428,7 +434,7 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("Values", "{{[1,2,3]");
verifyAndEnterColumnOptionInput("Labels", '{{["One","Two","Three"]');
// verifyAndEnterColumnOptionInput("Cell Background Color", "fakeString");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
selectDropdownOption(`${tableSelector.column(0)}:eq(0) .badge`, 1); // WIP (workaround needed)
cy.get(`${tableSelector.column(0)}:eq(1) .badge`).should(
"have.text",
@ -437,9 +443,9 @@ describe("Table", () => {
selectDropdownOption(`${tableSelector.column(0)}:eq(0) .badge`, 0);
cy.get(`${tableSelector.column(0)}:eq(0) .badge`).should(
"have.text",
"One"
"TwoOne"
);
// selectDropdownOption(`${tableSelector.column(0)}:eq(1) .badge:eq(1)`, 1);
selectDropdownOption(`${tableSelector.column(0)}:eq(1) .badge`, 1);
cy.get(`${tableSelector.column(0)}:eq(0) .badge`).should(
"have.text",
"One"
@ -458,7 +464,7 @@ describe("Table", () => {
// verifyAndEnterColumnOptionInput("Cell Background Color", "fakeString");
//WIP Not editble verify
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
cy.forceClickOnCanvas();
cy.get(`${tableSelector.column(0)}:eq(0) .badge`)
@ -493,7 +499,7 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("Labels", '{{["One","Two","Three"]');
// verifyAndEnterColumnOptionInput("Cell Background Color", "fakeString");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
verifyAndEnterColumnOptionInput("Custom rule", "fakeString");
deleteAndVerifyColumn("fake-dropdown");
@ -507,7 +513,7 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("Labels", '{{["One","Two","Three"]');
// verifyAndEnterColumnOptionInput("Cell Background Color", "fakeString");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
// //verifyRadio
deleteAndVerifyColumn("fake-radio");
@ -519,7 +525,7 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("Labels", '{{["One","Two","Three"]');
// verifyAndEnterColumnOptionInput("Cell Background Color", "fakeString");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
// //verify multiselect
deleteAndVerifyColumn("fake-multiselect");
@ -529,7 +535,7 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("key", "fakeString");
// verifyAndEnterColumnOptionInput("Active color", "green"); //use color Picker
// verifyAndEnterColumnOptionInput("Cell Background Color", "fakeString");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
deleteAndVerifyColumn("fake-toggleSwitch");
// //Toggle Switch
@ -539,7 +545,7 @@ describe("Table", () => {
verifyAndEnterColumnOptionInput("key", "fakeString");
// verifyAndEnterColumnOptionInput("Date Display format", "fakeString");
// verifyAndEnterColumnOptionInput("Cell Background Color", "blue");
cy.get('[data-cy="toggle-make-editable"]').click();
cy.get('[data-cy="make-editable-toggle-button"]').click();
// // verifyAndEnterColumnOptionInput("Date Parse Format", "fakeString");
@ -643,6 +649,9 @@ describe("Table", () => {
data.color,
data.boxShadowParam
);
cy.get(
commonWidgetSelector.draggableWidget(tableText.defaultWidgetName)
).click();
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
cy.get('[data-cy="label-table-type"]').verifyVisibleElement(
@ -660,6 +669,9 @@ describe("Table", () => {
.find("table")
.invoke("attr", "class")
.and("contain", "randomText");
cy.get(
commonWidgetSelector.draggableWidget(tableText.defaultWidgetName)
).click();
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
cy.get('[data-cy="table-type-fx-button"]').click();
@ -667,6 +679,7 @@ describe("Table", () => {
selectFromSidebarDropdown('[data-cy="dropdown-table-type"]', "Classic");
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(tableText.defaultWidgetName))
.click()
.find("table")
.invoke("attr", "class")
.and("contain", "classic");
@ -678,6 +691,7 @@ describe("Table", () => {
);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(tableText.defaultWidgetName))
.click()
.find("table")
.invoke("attr", "class")
.and("contain", "table-striped table-bordered ");
@ -695,6 +709,9 @@ describe("Table", () => {
`randomText`
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(tableText.defaultWidgetName)
).click();
cy.get(tableSelector.column(0))
.eq(0)
.invoke("attr", "class")
@ -705,6 +722,9 @@ describe("Table", () => {
cy.get('[data-cy="cell-size-fx-button"]').click();
selectFromSidebarDropdown('[data-cy="dropdown-cell-size"]', "Spacious");
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(tableText.defaultWidgetName)
).click();
cy.get(tableSelector.column(0))
.eq(0)
@ -718,7 +738,9 @@ describe("Table", () => {
);
selectColourFromColourPicker(`Text color`, data.color);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(tableText.defaultWidgetName))
.click()
.find("tbody")
.should(
"have.css",
@ -759,10 +781,10 @@ describe("Table", () => {
verifyAndModifyToggleFx("Server-side sort", "{{false}}", true);
verifyAndModifyToggleFx("Show download button", "{{true}}", true);
cy.notVisible('[data-tip="Download"]');
cy.notVisible('[data-tooltip-id="tooltip-for-download"]');
verifyAndModifyToggleFx("Show filter button", "{{true}}", true);
cy.notVisible('[data-tip="Filter data"]');
cy.notVisible('[data-tooltip-id="tooltip-for-filter-data"]');
cy.get('[data-cy="show-filter-button-toggle-button"]').click();
verifyAndModifyToggleFx("Server-side filter", "{{false}}", true);

View file

@ -91,7 +91,10 @@ describe("App Export Functionality", () => {
cy.get(appVersionSelectors.appVersionMenuField)
.should("be.visible")
.click();
createNewVersion((otherVersions = ["v2"]));
createNewVersion(otherVersions = ["v2"], currentVersion = "v1");
cy.wait(500);
cy.dragAndDropWidget("Toggle Switch", 50, 50);
cy.waitForAutoSave();
cy.get(appVersionSelectors.currentVersionField((otherVersions = "v2")))
.should("be.visible")
.invoke("text")

View file

@ -51,7 +51,7 @@ describe("App Import Functionality", () => {
force: true,
});
cy.verifyToastMessage(
commonSelectors.oldToastMessage,
commonSelectors.toastMessage,
importText.couldNotImportAppToastMessage
);
@ -60,7 +60,7 @@ describe("App Import Functionality", () => {
});
cy.get(".driver-close-btn").click();
cy.verifyToastMessage(
commonSelectors.oldToastMessage,
commonSelectors.toastMessage,
importText.appImportedToastMessage
);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
@ -104,7 +104,7 @@ describe("App Import Functionality", () => {
force: true,
});
cy.verifyToastMessage(
commonSelectors.oldToastMessage,
commonSelectors.toastMessage,
importText.appImportedToastMessage
);
cy.get(
@ -132,10 +132,11 @@ describe("App Import Functionality", () => {
cy.reload();
navigateToAppEditor(data.appReName);
cy.wait(500);
cy.get(appVersionSelectors.appVersionMenuField)
.should("be.visible")
.click();
createNewVersion((otherVersions = ["v2"]));
createNewVersion(otherVersions = ["v2"], currentVersion = "v1");
cy.get(appVersionSelectors.currentVersionField((otherVersions = "v2")))
.should("be.visible")
.click()
@ -174,7 +175,7 @@ describe("App Import Functionality", () => {
}
);
cy.verifyToastMessage(
commonSelectors.oldToastMessage,
commonSelectors.toastMessage,
importText.appImportedToastMessage
);
cy.get(appVersionSelectors.appVersionMenuField).click();

View file

@ -66,7 +66,7 @@ describe("Self host onboarding", () => {
.and("have.attr", "href")
.and("equal", "https://www.tooljet.com/privacy");
cy.clearAndType(commonSelectors.nameInputField, "The developer");
cy.clearAndType(commonSelectors.nameInputField, "The Developer");
cy.clearAndType(commonSelectors.emailInputField, "dev@tooljet.io");
cy.clearAndType(commonSelectors.passwordInputField, "password");
cy.get(commonSelectors.continueButton).click();

View file

@ -82,16 +82,18 @@ Cypress.Commands.add("appLogin", () => {
});
Cypress.Commands.add("waitForAutoSave", () => {
cy.get(commonSelectors.autoSave, { timeout: 10000 }).should(
cy.wait(200);
cy.get(commonSelectors.autoSave, { timeout: 20000 }).should(
"have.text",
commonText.autoSave
commonText.autoSave,
{ timeout: 20000 }
);
});
Cypress.Commands.add("createApp", (appName) => {
cy.get("body").then(($title) => {
if ($title.text().includes(commonText.introductionMessage)) {
cy.get(commonSelectors.emptyAppCreateButton).click();
cy.get(commonSelectors.emptyAppCreateButton).eq(0).click();
} else {
cy.get(commonSelectors.appCreateButton).click();
}
@ -170,7 +172,9 @@ Cypress.Commands.add(
Cypress.Commands.add("deleteApp", (appName) => {
cy.intercept("DELETE", "/api/apps/*").as("appDeleted");
cy.get(commonSelectors.appCard(appName))
.realHover()
.find(commonSelectors.appCardOptionsButton)
.realHover()
.click();
cy.get(commonSelectors.deleteAppOption).click();
cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
@ -203,7 +207,10 @@ Cypress.Commands.add("modifyCanvasSize", (x, y) => {
});
Cypress.Commands.add("renameApp", (appName) => {
cy.clearAndType(commonSelectors.appNameInput, appName);
cy.get(commonSelectors.appNameInput).type(
`{selectAll}{backspace}${appName}`,
{ force: true }
);
cy.waitForAutoSave();
});
@ -236,6 +243,11 @@ Cypress.Commands.add("notVisible", (dataCy) => {
cy.get(dataCy).should("not.be.visible");
}
});
const log = Cypress.log({
name: "notVisible",
displayName: "Not Visible",
message: dataCy,
});
});
Cypress.Commands.add("resizeWidget", (widgetName, x, y) => {

View file

@ -11,17 +11,30 @@ export const verifyComponent = (widgetName) => {
};
export const deleteComponentAndVerify = (widgetName) => {
cy.get(commonWidgetSelector.draggableWidget(widgetName)).click();
cy.get(`[data-cy="${widgetName}-delete-button"]`).last().click();
cy.get(commonWidgetSelector.draggableWidget(widgetName)).click().realHover();
cy.get(commonWidgetSelector.draggableWidget(widgetName)).realHover();
cy.get(`[data-cy="${widgetName}-delete-button"]`).last().realClick();
cy.verifyToastMessage(
`[class=go3958317564]`,
"Component deleted! (ctrl + Z to undo)"
);
cy.notVisible(commonWidgetSelector.draggableWidget(widgetName));
};
export const verifyComponentWithOutLabel=(component, defaultName, fakeName, appName, properties=[] )=>{
export const verifyComponentWithOutLabel = (
component,
defaultName,
fakeName,
appName,
properties = []
) => {
cy.dragAndDropWidget(component, 50, 50);
cy.get(`[data-cy="draggable-widget-${defaultName}"]`).click({ force: true });
verifyComponent(defaultName);
cy.resizeWidget(defaultName, 850, 600);
cy.resizeWidget(defaultName, 650, 400);
openEditorSidebar(defaultName);
editAndVerifyWidgetName(fakeName, properties);
@ -37,9 +50,4 @@ export const verifyComponentWithOutLabel=(component, defaultName, fakeName, appN
cy.get(commonSelectors.editorPageLogo).click();
cy.deleteApp(appName);
}
};

View file

@ -78,21 +78,23 @@ export const navigateToAppEditor = (appName) => {
.trigger("mousehover")
.trigger("mouseenter")
.find(commonSelectors.editButton)
.click();
.click({force:true});
//cy.wait("@appEditor");
};
export const viewAppCardOptions = (appName) => {
cy.get(commonSelectors.appCard(appName))
.find(commonSelectors.appCardOptionsButton)
.click();
cy.contains("div", appName)
.parent()
.within(() => {
cy.get(commonSelectors.appCardOptionsButton).invoke("click");
});
};
export const viewFolderCardOptions = (folderName) => {
cy.contains("div", folderName)
cy.get(commonSelectors.folderListcard(folderName))
.parent()
.within(() => {
cy.get(commonSelectors.folderCardOptions).invoke("click");
cy.get('[data-cy="folder-card-menu-icon"]').invoke('click');
});
};
@ -152,8 +154,7 @@ export const manageUsersPagination = (email) => {
};
export const searchUser = (email) => {
cy.clearAndType(commonSelectors.emailFilterInput, email);
cy.get(commonSelectors.filterButton).click();
cy.clearAndType(commonSelectors.inputUserSearch, email);
};
export const createWorkspace = (workspaceName) => {

View file

@ -72,6 +72,7 @@ export const addDefaultEventHandler = (message) => {
cy.get(commonWidgetSelector.eventHandlerCard).click();
cy.get(commonWidgetSelector.alertMessageInputField)
.find('[data-cy*="-input-field"]')
.eq(0)
.clearAndTypeOnCodeMirror(message);
};
@ -240,9 +241,16 @@ export const verifyAndModifyStylePickerFx = (
});
};
export const verifyWidgetColorCss = (widgetName, cssProperty, color) => {
export const verifyWidgetColorCss = (
widgetName,
cssProperty,
color,
innerProp = false
) => {
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(widgetName)).should(
cy.get(
innerProp ? widgetName : commonWidgetSelector.draggableWidget(widgetName)
).should(
"have.css",
cssProperty,
`rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 100})`

View file

@ -32,25 +32,22 @@ export const modifyAndVerifyAppCardIcon = (appName) => {
);
}
closeModal(commonText.closeButton);
// cy.get(dashboardSelector.appCardDefaultIcon).should("exist");
viewAppCardOptions(appName);
cy.get(commonSelectors.appCardOptions(commonText.changeIconOption)).click();
cy.get(dashboardSelector.appIcon(randomIcon)).click();
cy.get(dashboardSelector.appIcon(randomIcon)).first().click();
cancelModal(commonText.cancelButton);
// cy.get(dashboardSelector.appCardDefaultIcon).should("exist");
viewAppCardOptions(appName);
cy.get(commonSelectors.appCardOptions(commonText.changeIconOption)).click();
cy.get(dashboardSelector.appIcon(randomIcon)).click();
cy.get(dashboardSelector.appIcon(randomIcon)).first().click();
cy.get(dashboardSelector.changeButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
dashboardText.iconUpdatedToast
);
cy.get(dashboardSelector.appCardIcon(randomIcon)).should("exist");
cy.get(commonSelectors.appCard(appName)).should("exist");
cy.get(dashboardText.modalComponent).should("not.exist");
};

View file

@ -17,15 +17,15 @@ 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.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");
};
@ -66,7 +66,7 @@ export const createTableAndVerifyToastMessage = (
);
navigateToTable(tableName);
cy.get(databaseSelectors.idColumnHeader).verifyVisibleElement(
"have.text",
"contain",
databaseText.idColumnHeader
);
cy.get(databaseSelectors.noRecordsText).verifyVisibleElement(
@ -185,8 +185,10 @@ export const createNewColumnAndVerify = (
createNewColumnText.defaultValueLabel
);
cy.clearAndType(createNewColumnSelectors.columnNameInputField, columnName);
cy.get(createNewColumnSelectors.dataTypeDropdown).click();
cy.get(createNewColumnSelectors.dataTypeDropdown) .within(() => {
cy.contains(`Select data type`).click();
cy.contains(`[id*="react-select-"]`, columnDataType).click();
})
if (defaultValue) {
cy.clearAndType(
createNewColumnSelectors.defaultValueInputField,
@ -215,7 +217,7 @@ export const createNewColumnAndVerify = (
);
cy.get(databaseSelectors.columnHeader(columnName)).verifyVisibleElement(
"have.text",
"contain",
`${String(columnName).toLowerCase().replace(/\s+/g, "-")}`
);
};
@ -391,6 +393,7 @@ export const sortOperation = (tableName, columnName = [], order = []) => {
cy.get(databaseSelectors.idColumnHeader).should("be.visible");
};
export const deleteCondition = (selector, columnName = [], deleteIcon) => {
cy.wait(500);
cy.get(selector).click();
cy.get(".card-body").eq(1).should("be.visible");
for (let i = 0; i < columnName.length; i++) {

View file

@ -0,0 +1,28 @@
export const selectEvent = (event, action) => {
cy.get('[data-cy="add-event-handler"]').click()
cy.get('[data-cy="event-handler"]').eq(0).click()
cy.get('[data-cy="event-selection"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${event}{enter}`);
cy.get('[data-cy="action-selection"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${action}{enter}`);
};
export const selectCSA = (
component,
componentAction,
dbounce = `{selectAll}{backspace}`
) => {
cy.get('[data-cy="action-options-component-selection-field"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${component}`);
cy.get('[data-cy="action-options-action-selection-field"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${componentAction}`);
cy.get('[data-cy="-input-field"]').type(`{selectAll}{backspace}${debounce}`);
};

View file

@ -70,7 +70,7 @@ export const createNewVersion = (newVersion = [], version) => {
export const clickOnExportButtonAndVerify = (buttonText, appName) => {
cy.get(commonSelectors.buttonSelector(buttonText)).click();
cy.wait(1000);
cy.exec("ls ./cypress/downloads/").then((result) => {
const downloadedAppExportFileName = result.stdout.split("\n")[0];
expect(downloadedAppExportFileName).to.have.string(appName.toLowerCase());

View file

@ -9,7 +9,7 @@ export const deleteInnerWidget = (widgetName, innerWidgetName) => {
);
cy.get("@innerWidget").first().click();
cy.get(`[data-cy="${innerWidgetName}-delete-button"]`)
.click()
.realClick()
.should("not.exist");
});
};

View file

@ -166,10 +166,6 @@ export const gitSSOPageElements = () => {
export const passwordPageElements = () => {
cy.get(ssoSelector.passwordEnableToggle).then(($el) => {
if ($el.is(":checked")) {
cy.get(ssoSelector.statusLabel).verifyVisibleElement(
"have.text",
ssoText.enabledLabel
);
cy.get(ssoSelector.passwordEnableToggle).uncheck();
cy.get(commonSelectors.modalComponent).should("be.visible");
cy.get(commonSelectors.modalMessage).verifyVisibleElement(
@ -181,44 +177,27 @@ export const passwordPageElements = () => {
commonSelectors.toastMessage,
ssoText.passwordDisabledToast
);
cy.get(ssoSelector.statusLabel).verifyVisibleElement(
"have.text",
ssoText.disabledLabel
);
cy.get(ssoSelector.passwordEnableToggle).check();
cy.verifyToastMessage(
commonSelectors.toastMessage,
ssoText.passwordEnabledToast
);
cy.get(ssoSelector.statusLabel).verifyVisibleElement(
"have.text",
ssoText.enabledLabel
);
} else {
cy.get(ssoSelector.statusLabel).verifyVisibleElement(
"have.text",
ssoText.disabledLabel
);
cy.get(ssoSelector.passwordEnableToggle).check();
cy.verifyToastMessage(
commonSelectors.toastMessage,
ssoText.passwordEnabledToast
);
cy.get(ssoSelector.statusLabel).verifyVisibleElement(
"have.text",
ssoText.enabledLabel
);
cy.get(ssoSelector.passwordEnableToggle).uncheck();
cy.verifyToastMessage(
commonSelectors.toastMessage,
ssoText.passwordDisabledToast
);
cy.get(ssoSelector.statusLabel).verifyVisibleElement(
"have.text",
ssoText.disabledLabel
);
cy.get(ssoSelector.passwordEnableToggle).check();
}
});

View file

@ -1,19 +1,33 @@
import { path } from "Texts/common";
import { commonSelectors } from "Selectors/common";
import { usersText } from "Texts/manageUsers";
import { commonSelectors } from "Selectors/common"
import { usersText } from "Texts/manageUsers"
import { usersSelector } from "Selectors/manageUsers";
import { ssoSelector } from "Selectors/manageSSO";
import { ssoText } from "Texts/manageSSO";
import * as common from "Support/utils/common";
import { commonText } from "../../constants/texts/common";
import { commonText } from "Texts/common";
export const manageUsersElements = () => {
cy.get(commonSelectors.breadcrumbTitle).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(
commonText.breadcrumbworkspaceSettingTitle
);
});
cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement( "have.text",usersText.breadcrumbUsersPageTitle);
for (const element in usersSelector.usersElements) {
cy.get(usersSelector.usersElements[element]).verifyVisibleElement(
"have.text",
usersText.usersElements[element]
);
}
cy.get(usersSelector.usersPageTitle).should(($el) => {
expect($el.contents().last().text().trim()).to.eq(
usersText.usersPageTitle
);
});
cy.get(commonSelectors.inputUserSearch).should("be.visible")
common.searchUser(usersText.adminUserEmail);
cy.contains("td", usersText.adminUserEmail)
.parent()
@ -35,51 +49,56 @@ export const manageUsersElements = () => {
usersText.adminUserState
);
});
cy.get(commonSelectors.emailFilterInput).should("be.visible");
cy.get(commonSelectors.firstNameFilterInput).should("be.visible");
cy.get(commonSelectors.lastNameFilterInput).should("be.visible");
cy.get(commonSelectors.clearFilterButton).should("be.visible");
cy.get(commonSelectors.userStatusSelect).should("be.visible");
cy.get(usersSelector.userFilterInput).should("be.visible");
cy.get(usersSelector.inviteUserButton)
.verifyVisibleElement("have.text", usersText.inviteUserButton)
cy.get(usersSelector.buttonAddUsers)
.verifyVisibleElement("have.text", usersText.buttonAddUsers)
.click();
cy.get(usersSelector.cardTitle).verifyVisibleElement(
"have.text",
usersText.cardTitle
);
cy.get(usersSelector.firstNameInput).should("be.visible");
cy.get(usersSelector.lastNameInput).should("be.visible");
cy.get(usersSelector.emailLabel).verifyVisibleElement(
"have.text",
usersText.emailLabel
);
cy.get(usersSelector.lastNameInput).should("be.visible");
cy.get(usersSelector.cancelButton).verifyVisibleElement(
"have.text",
usersText.cancelButton
);
cy.get(usersSelector.createUserButton).verifyVisibleElement(
"have.text",
usersText.createUserButton
);
cy.get(usersSelector.cancelButton).click();
cy.get(usersSelector.buttonInviteWithEmail).verifyVisibleElement( "have.text",usersText.buttonInviteWithEmail);
cy.get(usersSelector.buttonUploadCsvFile).verifyVisibleElement("have.text",usersText.buttonUploadCsvFile);
cy.get(usersSelector.addUsersCardTitle).verifyVisibleElement("have.text",usersText.addUsersCardTitle);
cy.get(commonSelectors.labelFullNameInput).verifyVisibleElement("have.text",commonText.labelFullNameInput);
cy.get(commonSelectors.inputFieldFullName).should("be.visible");
cy.get(commonSelectors.labelEmailInput).verifyVisibleElement("have.text",commonText.labelEmailInput);
cy.get(commonSelectors.inputFieldEmailAddress).should("be.visible");
cy.get(commonSelectors.cancelButton).verifyVisibleElement("have.text",usersText.cancelButton);
cy.get(usersSelector.buttonInviteUsers).verifyVisibleElement(
"have.text",
usersText.buttonInviteUsers
);
cy.get(commonSelectors.cancelButton).click();
cy.get(usersSelector.addUsersCardTitle).should("not.exist")
cy.get(usersSelector.buttonAddUsers).click();
cy.get(commonSelectors.closeButton).click();
cy.get(usersSelector.addUsersCardTitle).should("not.exist")
cy.get(usersSelector.buttonAddUsers).click();
cy.get(usersSelector.addUsersCardTitle).verifyVisibleElement("have.text", usersText.addUsersCardTitle);
cy.get(usersSelector.buttonUploadCsvFile).click();
cy.get(usersSelector.helperTextBulkUpload).verifyVisibleElement("have.text", usersText.helperTextBulkUpload);
cy.get(usersSelector.buttonDownloadTemplate).verifyVisibleElement("have.text", usersText.buttonDownloadTemplate);
cy.get(usersSelector.iconBulkUpload).should("be.visible")
cy.get(usersSelector.helperTextSelectFile).verifyVisibleElement("have.text", usersText.helperTextSelectFile);
cy.get(usersSelector.helperTextDropFile).verifyVisibleElement("have.text", usersText.helperTextDropFile);
cy.get(usersSelector.inputFieldBulkUpload).should("exist")
cy.get(usersSelector.buttonUploadUsers).verifyVisibleElement("have.text", usersText.buttonUploadUsers);
cy.get(usersSelector.inviteBulkUserButton).verifyVisibleElement("have.text",usersText.inviteBulkUserButton).click();
cy.get(usersSelector.bulkUserUploadPageTitle).verifyVisibleElement("have.text",usersText.bulkUserUploadPageTitle);
cy.get(usersSelector.bulkUSerUploadInput).should("be.visible");
cy.get(usersSelector.downloadTemplateButton).verifyVisibleElement("have.text",usersText.downloadTemplateButton);
cy.get(usersSelector.cancelButton).verifyVisibleElement("have.text",usersText.cancelButton);
};
export const inviteUser = (firstName, lastName, email) => {
cy.get(usersSelector.inviteUserButton).click();
cy.clearAndType(usersSelector.firstNameInput, firstName);
cy.clearAndType(usersSelector.lastNameInput, lastName);
cy.clearAndType(usersSelector.emailInput, email);
export const inviteUser = (firstName, email) => {
cy.get(usersSelector.buttonAddUsers).click();
cy.clearAndType(commonSelectors.inputFieldFullName, firstName);
cy.clearAndType(commonSelectors.inputFieldEmailAddress, email);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.buttonInviteUsers).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
usersText.userCreatedToast
@ -91,7 +110,7 @@ export const inviteUser = (firstName, lastName, email) => {
cy.contains("td", email)
.parent()
.within(() => {
cy.get("td img").click();
cy.get(usersSelector.copyInvitationLink).click();
});
cy.verifyToastMessage(
commonSelectors.toastMessage,
@ -100,15 +119,13 @@ export const inviteUser = (firstName, lastName, email) => {
cy.get("@copyToClipboardPrompt").then((prompt) => {
common.logout();
cy.visit(prompt.args[0][1]);
cy.url().should("include", path.confirmInvite);
});
};
export const addNewUser = (firstName, lastName, email) => {
export const addNewUser = (firstName, email) => {
cy.intercept("POST", "/api/organization_users").as("appLibrary");
cy.clearAndType(usersSelector.firstNameInput, firstName);
cy.clearAndType(usersSelector.lastNameInput, lastName);
cy.clearAndType(usersSelector.emailInput, email);
cy.clearAndType(commonSelectors.inputFieldFullName, firstName);
cy.clearAndType(commonSelectors.inputFieldEmailAddress, email);
cy.get(usersSelector.createUserButton).click();
cy.wait("@appLibrary").then((res) => {
@ -121,7 +138,6 @@ export const addNewUser = (firstName, lastName, email) => {
};
export const confirmInviteElements = () => {
cy.url().should("include", '/confirm');
cy.get(commonSelectors.invitePageHeader).verifyVisibleElement(
"have.text",
commonText.invitePageHeader

View file

@ -0,0 +1,59 @@
import { commonWidgetSelector } from "Selectors/common";
import {
verifyAndModifyStylePickerFx,
selectColourFromColourPicker,
verifyWidgetColorCss,
} from "Support/utils/commonWidget";
export const launchModal = (componentName) => {
cy.get(launchButton(componentName)).click();
};
export const launchButton = (componentName) => {
return `[data-cy="draggable-widget-${componentName
.toLowerCase()
.replace(" ", "-")}-launch-button"]`;
};
export const verifySize = (size) => {
const className = {
Small: "sm",
Medium: "lg",
Large: "xl",
};
cy.get('[data-cy="dropdown-modal-size"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${size}{enter}`);
cy.get(
`[class="modal-dialog modal-${className[size]} modal-dialog-scrollable"]`
).should("exist");
};
export const closeModal = (componentName) => {
cy.get('[data-cy="modal-close-button"]').realClick();
};
export const addAndVerifyColor = (
section,
defaultColor,
color,
dataCy,
type = "background-color"
) => {
verifyAndModifyStylePickerFx(section, defaultColor, "data.colourHex");
cy.get(commonWidgetSelector.parameterFxButton(section)).click();
selectColourFromColourPicker(section, color);
verifyWidgetColorCss(dataCy, type, color, true);
closeModal("modal1");
launchModal("modal1");
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click({ force: true });
};
export const typeOnFx = (fx, data) => {
cy.get(commonWidgetSelector.parameterFxButton(fx)).eq(1).realClick();
cy.get(commonWidgetSelector.parameterInputField(fx)).clearAndTypeOnCodeMirror(
data
);
};

View file

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

View file

@ -43,7 +43,7 @@ export const selectDropdownOption = (inputSelector, option) => {
};
const click = () => {
cy.get(inputSelector).click();
cy.get(inputSelector).realClick();
cy.wait(500);
cy.get("body").then(($body) => {
if ($body.find('[data-index="0"]').length == 0) {
@ -55,47 +55,46 @@ export const selectDropdownOption = (inputSelector, option) => {
click();
cy.get(
isNaN(option)
? `[data-index="${data[option]}"]`
: `[data-index="${option}"]`
).click();
? `[data-index="${data[option]}"]>.select-search-option:eq(0)`
: `[data-index="${option}"]>.select-search-option:eq(0)`
).click({ force: true });
};
export const verifyAndEnterColumnOptionInput = (label, value) => {
cy.get(`[data-cy="input-and-label-${cyParamName(label)}"]`)
.find("label")
.should("have.text", label);
cy.get(`[data-cy="input-and-label-${cyParamName(label)}"]`).type(
`{selectAll}{backspace}${value}`
);
cy.get(`[data-cy="input-and-label-${cyParamName(label)}"]`)
.realClick()
.realPress(["Meta", "A"])
.realType(
`{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}${value}`
);
};
export const addAndOpenColumnOption = (name, type) => {
cy.get('[data-cy="button-add-column"]').click();
cy.get('[data-cy="button-add-column"]')
.parents(".accordion-body")
.find('[data-cy*="column-"]')
.find('[data-cy*="column-new_column"]')
.last()
.click();
selectDropdownOption(
'[data-cy="dropdown-column-type"] > .select-search',
type
);
selectDropdownOption('[data-cy="dropdown-column-type"]>>:eq(0)', type);
verifyAndEnterColumnOptionInput("Column name", name);
};
export const deleteAndVerifyColumn = (columnName) => {
cy.get(`[data-cy="button-delete-${columnName}"]`).click();
cy.notVisible(`[data-cy="column-${columnName}"]`);
cy.forceClickOnCanvas();
cy.notVisible(tableSelector.columnHeader(columnName));
};
export const verifyInvalidFeedback = (columnIndex = 0, rowIndex = 0, text) => {
cy.get(tableSelector.column(columnIndex))
.eq(rowIndex)
.find('[class="invalid-feedback"]')
.find(">>>>:eq(1)")
.should("have.text", text);
cy.forceClickOnCanvas();
// cy.forceClickOnCanvas();
};
export const addInputOnTable = (
@ -107,6 +106,7 @@ export const addInputOnTable = (
cy.forceClickOnCanvas();
cy.get(tableSelector.column(columnIndex))
.eq(rowIndex)
.click()
.find(type)
.click()
.type(`{selectAll}{backspace}${value}`);
@ -165,14 +165,18 @@ export const dataCsvAssertionHelper = (data) => {
return dataArray;
};
export const addFilter =(data=[{column:'name', operation: "contains", value: 'Sarah'}], freshFilter=false)=>{
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()
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()
@ -180,10 +184,11 @@ export const addFilter =(data=[{column:'name', operation: "contains", value: 'Sa
cy.get(tableSelector.filterSelectOperation(index))
.click()
.type(`${filter.operation}{enter}`);
if(filter.value){
cy.get(tableSelector.filterInput(index)).type(`{selectAll}{del}${filter.value}`);
}
if (filter.value) {
cy.get(tableSelector.filterInput(index)).type(
`{selectAll}{del}${filter.value}`
);
}
});
cy.get(tableSelector.buttonCloseFilters).click()
}
cy.get(tableSelector.buttonCloseFilters).click();
};

View file

@ -6,7 +6,7 @@ import * as common from "Support/utils/common";
import { path } from "Texts/common";
import { groupsSelector } from "Selectors/manageGroups";
import { groupsText } from "Texts/manageGroups";
import { dashboardSelector } from "../../constants/selectors/dashboard";
import { dashboardSelector } from "Selectors/dashboard";
export const adminLogin = () => {
common.logout();
@ -59,9 +59,9 @@ export const reset = () => {
});
};
export const addNewUserMW = (firstName, lastName, email, companyName) => {
export const addNewUserMW = (firstName, email, companyName) => {
common.navigateToManageUsers();
users.inviteUser(firstName, lastName, email);
users.inviteUser(firstName, email);
cy.clearAndType(commonSelectors.passwordInputField, usersText.password);
cy.get(commonSelectors.acceptInviteButton).click();
cy.get(commonSelectors.workspaceName).verifyVisibleElement(

View file

@ -2,112 +2,158 @@ 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 { 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();
}
cy.get(appVersionSelectors.currentVersionField(value))
.should("be.visible")
.click();
cy.contains(appVersionText.createNewVersion).should("be.visible");
cy.contains(appVersionText.createNewVersion).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();
})
}
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();
};
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();
cy.get(appVersionSelectors.currentVersionField(currentVersion)).then(
($ele) => {
if ($ele.hasClass("color-light-green")) {
cy.contains(releasedVersionText.releasedModalText).should("be.visible");
closeModal(commonText.closeButton);
}
}
);
navigateToEditVersionModal(currentVersion);
cy.get(editVersionSelectors.versionNameInputField).verifyVisibleElement(
"have.value",
currentVersion
);
}
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
);
}
cy.clearAndType(editVersionSelectors.versionNameInputField, newVersion[0]);
cy.get(editVersionSelectors.saveButton).click();
cy.wait(500);
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
);
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
);
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");
cy.contains("Release").click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
releasedVersionText.releasedToastMessage(currentVersion)
);
cy.forceClickOnCanvas();
cy.wait(2000);
verifyModal(
appVersionText.createNewVersion,
appVersionText.createNewVersion,
appVersionSelectors.versionNameInputField
);
cy.contains(releasedVersionText.releasedModalText).should("be.visible");
cy.wait(500);
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)
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);
};

View file

@ -17,6 +17,7 @@ The user details entered while setting up ToolJet will have Super Admin privileg
| Manage Groups in their workspace (Create Group/Add or Delete Users from groups/ Modify Group Permissions) | ✅ | ✅ |
| Manage SSO in their workspace | ✅ | ✅ |
| Manage Workspace Variables in their workspace | ✅ | ✅ |
| [Manage Global datasources for the user group in their workspace](/docs/next/data-sources/overview#permissions) | ✅ | ✅ |
| [Access any user's personal workspace (create, edit or delete apps)](#access-any-workspace) | ❌ | ✅ |
| [Archive Admin or any user of any workspace](#archiveunarchive-users) | ❌ | ✅ |
| [Access any user's ToolJet database (create, edit or delete database)](#access-tooljet-db-in-any-workspace) | ❌ | ✅ |

View file

@ -41,3 +41,6 @@ For VSCode users, you can set the formatter to `ESLint` in the [**settings.json*
1. **Node version 18.3.0**
2. **npm version 8.11.0**
:::tip
It is recommended to check the VSCode **Setting.json**(Press `ctrl/cmnd + P` and search `>Settings (JSON)`) file to ensure there are no overrides to the eslint config rules. Comment the following rules for eslint: **eslint.options: {...}**.
:::

View file

@ -0,0 +1,11 @@
# grpc
ToolJet can connect to GRPC databases to read and write data.
- [Connection](#connection)
- [Getting Started](#querying-grpc)
## Connection
## Querying GRPC

View file

@ -3,9 +3,13 @@ id: overview
title: Overview
---
# Datasources : Overview
# Global Datasources : Overview
Datasources pull in and push data to any source including databases, external APIs, or services.
Global datasources pull in and push data to any source including databases, external APIs, or services. Once a global datasource is connected to a workspace, the connection can be shared with any app of that workspace.
:::caution
Global datasources are available only on **ToolJet version 2.3.0 and above**.
:::
<div style={{textAlign: 'center'}}>
@ -13,30 +17,115 @@ Datasources pull in and push data to any source including databases, external AP
</div>
## Connecting datasources
## Connecting global datasources
1. After logging in to ToolJet, create a new app from the dashboard
1. From the ToolJet dashboard, go to the **global datasources page** from the left sidebar.
<div style={{textAlign: 'center'}}>
2. There are two ways for connecting a datasource. You can connect from:
1. **Left-sidebar**: On the left sidebar, click on the `datasource` icon and then click on the `+ add datasource` button
<img className="screenshot-full" src="/img/datasource-reference/overview/global.png" alt="Datasources: Overview" />
<div style={{textAlign: 'center'}}>
</div>
<img className="screenshot-full" src="/img/datasource-reference/overview/ls2.png" alt="Datasources: Overview" width="400"/>
2. Click on the **Add new datasource** button, a modal will pop-up with all the available global datasources.
<div style={{textAlign: 'center'}}>
</div>
<img className="screenshot-full" src="/img/datasource-reference/overview/popup.png" alt="Datasources: Overview" />
2. **Query Panel**: Go to the query panel at the bottom, click on the `+Add` button and then click `Add datasource` button
</div>
<div style={{textAlign: 'center'}}>
3. Select the datasource, enter the **Credentials** and **Save** the datasource.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/qp2.png" alt="Datasources: Overview"/>
<img className="screenshot-full" src="/img/datasource-reference/overview/connection.png" alt="Datasources: Overview" />
</div>
</div>
3. Follow the steps in the **[Datasource Library](/docs/data-sources/airtable)** specific to the desired datasource
4. Now, go back to the dashboard, create a new app, and the datasource will be available on the query panel under **Global Datasources**. Added datasources will be available on any of the **existing** or the **new applications**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/globalquery.png" alt="Datasources: Overview" />
</div>
5. You can now create queries of the connected global datasource. From the queries, you'll be able to switch to **different connections** of the same datasource if there are more than one connections created.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/switch.png" alt="Datasources: Overview" />
</div>
## Changing scope of datasources of an app created on older versions of ToolJet
On ToolJet versions below 2.3.0, the datasource connection was made from within the individual apps. To make it backward compatible, we added an option to change the scope of the datasources and make it global datasource.
1. If you open an app created on previos versions of ToolJet, you'll find the datasource manager on the left sidebar of the App Builder.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/leftsidebar.png" alt="Datasources: Overview" />
</div>
2. Click on the kebab menu next to the connected datasource, select the **change scope** option.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/changescope.png" alt="Datasources: Overview" />
</div>
3. Once you change the scope of the datasource and make it global, you'll see that the **datasource manager** is removed from the left sidebar and now you'll find the datasource on the **query panel** under Global Datasources. You can now configure the datasource fromt the Global Datasource page on the **dashboard**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/queryadd.png" alt="Datasources: Overview" />
</div>
## Default datasources
By default, 4 datasources will be available on every app on ToolJet:
- **[ToolJet Database](/docs/tooljet-database/)**
- **[RestAPI](/docs/data-sources/restapi/)**
- **[Run JavaScript Query](/docs/data-sources/run-js/)**
- **[Run Python Query](/docs/data-sources/run-py/)**
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/default.png" alt="Datasources: Overview" />
</div>
## Permissions
Only **Admins** and **[Super Admins](/docs/Enterprise/superadmin)** of the workspace can change the **[Permissions](/docs/tutorial/manage-users-groups#group-properties)** for Global Datasource.
From **Workspace Settings** -> **Groups Settings**, Admins and Super Admins can set the permission for a user group to:
- **Create** and **Delete** datasources onto that workspace. If **Create** permission is enabled then the users can add new global datasources and **edit** the datasources as well but cannot **delete** it, and if only **Delete** permission is set then the users of the group will only be able to delete the connected datasources on the workspace.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/create.png" alt="Datasources: Overview" />
</div>
- If any of the permission(Create or Delete) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/error.png" alt="Datasources: Overview" />
</div>
- **View** or **Edit** allowed global datasources from the **Datasources** tab. If only **View** permission is set then the users of the group will only be able to connect to the allowed datasource, and if only **Edit** permission is set then the users of the group will be able to update the credentials of the allowed datasources.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/view.png" alt="Datasources: Overview" />
</div>
- If any of the permission(View or Edit) is not enabled for a user group then the users of the group will get an error toast when they try to Add or Delete the global datasource.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/overview/edit.png" alt="Datasources: Overview" />
</div>
:::info
ToolJet allows you to transform the data returned by datasources using **[Transformations](/docs/tutorial/transformations)**
:::

View file

@ -5,18 +5,24 @@ title: SMTP
# SMTP
SMTP plugin can connect ToolJet applications to **SMTP servers** for sending emails.
The SMTP datasource facilitates the connection between ToolJet applications and email servers, enabling the apps to send emails.
## Connection
A SMTP server can be connected with the following credentails:
- **Host**
- **Port**
- **User**
To connect to an SMTP server, the following credentials are typically required:
- **Host**
- **Port**
- **Username**
- **Password**
:::info
You can also test your connection before saving the configuration by clicking on `Test Connection` button.
:::tip Finding configuration details:
The SMTP configuration details like host and port can usually be obtained from your email service provider. Here are some general settings for the most commonly used email providers:
- **Gmail**: `Host`: smtp.gmail.com; `Port`: 587 or 465 (SSL); `Username`: your full Gmail email address; `Password`: your Gmail password.
- **Yahoo Mail**: `Host`: smtp.mail.yahoo.com; `Port`: 465 (SSL); `Username`: your Yahoo Mail email address; `Password`: your Yahoo Mail password.
- **Outlook.com/Hotmail**: `Host`: smtp.office365.com; `Port`: 587 or 465 (SSL); `Username`: your Outlook.com/Hotmail email address; `Password`: your Outlook.com/Hotmail password.
Before saving the configuration, it's possible to test the connection by clicking the "Test Connection" button.
:::
<div style={{textAlign: 'center'}}>
@ -27,22 +33,25 @@ You can also test your connection before saving the configuration by clicking on
## Querying SMTP
Go to the query manager at the bottom panel of the editor and click on the `+` button on the left to create a new query. Select `SMTP` from the datasource dropdown.
To create a query for sending an email, follow these steps:
To create a query for sending email, you will need to provide the following properties:
- **From** `required` : Email address of the sender
- **From Name** : Name of the sender
- **To** `required` : Recipient's email address
- **Subject** : Subject of the email
1. Open the query panel located at the bottom panel of the editor.
2. Click the `+Add` button on the left to create a new query.
3. Select `SMTP` from the global datasource.
4. Provide the following properties:
- **From** `required` : Email address of the sender
- **From Name** : Name of the sender
- **To** `required` : Recipient's email address
- **CC mail to** : Email address of the recipients that will receive a copy of the email, and their email addresses will be visible to other recipients.
- **BCC mail to** : Email address of the recipients that will receive a copy of the email but the email addressed will be hidden to other recipients.
- **Subject** : Subject of the email.
- **Body** : You can enter the body text of the email in either raw text or html format, in their respective fields.
- **Attachments** : You can add attachments to an SMTP query by referencing the file from the File Picker component in the attachments field.
For instance, you can set the `Attachments` field value to `{{ components.filepicker1.file }}` or pass an array of `{{ name: 'filename.jpg', dataURL: '......' }}` objects to include attachments.
<img className="screenshot-full" src="/img/datasource-reference/smtp/query1.png" alt="smtp query1" />
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/smtp/querysmtp.png" alt="smtp connect" />
- **Body** : You can enter the body text either in the form of `raw text` or `html` in their respective fields.
- **Attachments** : Attachments can be added to a SMTP query by referencing the file from the `File Picker` component in the attachments field.
For example, you can set the `Attachments` field value to `{{ components.filepicker1.file }}` or you can pass an array of `{{ name: 'filename.jpg', dataURL: '......' }}` object to accomplish this.
<img className="screenshot-full" src="/img/datasource-reference/smtp/query2.png" alt="smtp query2" />
</div>

View file

@ -107,7 +107,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
### Create a new application
1. To create a new ToolJet application, go to the **Dashboard** -> **New App from scratch**.
1. To create a new ToolJet application, go to the **Dashboard** -> **Create new application**.
<div style={{textAlign: 'center'}}>
@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
</div>
:::info
ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **Components Catalog** to learn more.
ToolJet application's User interface is constructed using Components like Tables, Forms, Charts, or Buttons etc. Check **[Components Catalog](/docs/widgets/overview)** to learn more.
:::
### Build queries and bind data to UI
@ -156,7 +156,7 @@ ToolJet application's User interface is constructed using Components like Tables
</div>
:::info
ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **Datasource Catalog** to learn more.
ToolJet can connect to several databases, APIs and external services to fetch and modify data. Check **[Datasource Catalog](/docs/data-sources/overview)** to learn more.
:::
2. Choose a **Table** from the dropdown, Select the **List rows** option from the **Operation** dropdown, You can leave other query parameters. Scroll down and enable **Run this query on application load** - this will trigger the query when the app is loaded.
@ -199,8 +199,8 @@ ToolJet application's User interface is constructed using Components like Tables
</div>
:::info
- You can manipulate the data returned by the queries using **Transformations**
- You can also **Run JS query** or **Python query** to perform custom behavior inside ToolJet
- You can manipulate the data returned by the queries using **[Transformations](/docs/tutorial/transformations)**
- You can also **[Run JavaScript code](/docs/data-sources/run-js)** or **[Run Python code](/docs/data-sources/run-py)** to perform custom behavior inside ToolJet
:::
### Preview, Release and Share app
@ -210,7 +210,7 @@ ToolJet application's User interface is constructed using Components like Tables
3. **Share** option allows you to share the **released version** of the application with other users or you can also make the app **public** and anyone with the URL will be able to use the app.
:::tip
You can control how much access to users have to your ToolJet apps and resources using **Org Management**.
You can control how much access to users have to your ToolJet apps and resources using **[Org Management](/docs/tutorial/manage-users-groups)**.
:::
## What Can I Do With ToolJet

View file

@ -0,0 +1,23 @@
---
id: intentionally-fail-js-query
title: Intentionally fail a RunJS query
---
In this how-to guide, we will create a RunJS query that will throw an error.
- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance.
```js
throw new ReferenceError('This is a reference error.');
```
- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/failjs/failjs.gif" alt="Intentionally fail a RunJS query" />
</div>
:::info
Most common use-case for intentionally failing a query is **debugging**.
:::

View file

@ -0,0 +1,173 @@
---
id: use-s3-signed-url-to-upload-docs
title: Use S3 signed URL to upload documents
---
# Use S3 signed URL to upload documents
In this how-to guide, you'll learn to upload documents to S3 buckets using the **S3 signed URL** from a ToolJet application.
For this guide, We are going to use one of the existing templates on ToolJet: **S3 File explorer**
:::info using Templates
On ToolJet Dashboard, Click on the down arrow on the right of the **New App** button, from the dropdown choose the **Choose from template** option.
:::
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/template.png" alt="Use S3 pre-signed URL to upload documents: Choose template" width="700"/>
</div>
- Once you've created a new app using the template, you'll be prompted to create a **new version** of the existing version. After creating a new version, you'll be able to make changes in the app.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/newversion.png" alt="Use S3 pre-signed URL to upload documents: new version"/>
</div>
- Go to the **datasource manager** on the left-sidebar, you'll find that the **AWS S3 datasource** is already added. All you need to do is update the datasource **credentials**.
:::tip
Check the [AWS S3 datasource reference](/docs/data-sources/s3) to learn more about connnection and choosing your preferred authentication method.
:::
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/s3connect.png" alt="Use S3 pre-signed URL to upload documents: add datasource"/>
</div>
- Once the datasource is connected successfully, go to the query manager and **Run** the **getBuckets** query. The operation selected in the getBuckets query is **List Buckets** which will fetch an array of all the buckets.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/getbuckets.png" alt="Use S3 pre-signed URL to upload documents: getBuckets query"/>
</div>
- Running the **getBuckets** query will load all the buckets in the dropdown in the app.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/dropdown.png" alt="Use S3 pre-signed URL to upload documents: loading buckets"/>
</div>
- Select a **bucket** from the dropdown and click on the **Fetch files** button to list all the files from the selected bucket on the table. The **Fetch files** button has the event handler added that triggers the **s32** query, the **s32** query uses the **List objects in a bucket** operation, and the bucket field in the query gets the value dynamically from the dropdown.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/fetchfiles.png" alt="Use S3 pre-signed URL to upload documents: list objects in a bucket"/>
</div>
- Let's go to the **uploadToS3** query and update the field values:
- **Operation**: Signed URL for upload
- **Bucket**: `{{components.dropdown1.value}}` this will fetch the dynamic value from the dropdown
- **Key**: `{{components.filepicker1.file[0].name}}` this will get the file name from the filepickers exposed variables
- **Expires in:** This sets an expiration time of URL, by default its `3600` seconds (1 hour)
- **Content Type**: `{{components.filepicker1.file[0].type}}` this will get the file type from the filepickers exposed variables
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/upload.png" alt="Use S3 pre-signed URL to upload documents"/>
</div>
- Create two **RunJS** queries:
- Create a **runjs1** query and copy-paste the code below. This query gets the **base64data** from the file picker and convert the file's `base64Data` to into `BLOB`, and returns the file object.
```js
const base64String = components.filepicker1.file[0].base64Data
const decodedArray = new Uint8Array(atob(base64String).split('').map(c => c.charCodeAt(0)));
const file = new Blob([decodedArray], { type: components.filepicker1.file[0].type });
const fileName = components.filepicker1.file[0].name;
const fileObj = new File([file], fileName);
return fileObj
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/runjs1.png" alt="Use S3 pre-signed URL to upload documents"/>
</div>
- Create another **runjs2** query and copy-paste the code below. This query gets the data(file object) returned by the first runjs query, the url returned by the **uploadToS3** query, and then makes PUT request.
```js
const file = queries.runjs2.data
const url = queries.s31.data.url
fetch(url, {
method: 'PUT',
body: file,
mode: 'cors',
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json'
}
})
.then(response => console.log('Upload successful!'))
.catch(error => console.error('Error uploading file:', error));
```
:::warning Enable Cross Origin Resource Sharing(CORS)
- For the file to be uploaded successfully, you will need to add the CORS policies from the **Permissions** tab of your **Bucket** settings. Here's a sample CORS:
```json
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"POST"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
```
:::
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/runjs2.png" alt="Use S3 pre-signed URL to upload documents"/>
</div>
- Go to the **uploadToS3**, scroll down and add an event handler to the **uploadToS3** query. Select the **Query Success** event, **Run Query** as the action, and **runjs1** as the query to be triggered. **Save** the query.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/eventhandlerupload.png" alt="Use S3 pre-signed URL to upload documents"/>
</div>
- Let's go to the **runjs1** query and add the event handler to run a query on query success event, similar to how we did in the previous step. In the event handler, choose **runjs2** query. **Save** the query.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/eventhandlerrunjs2.png" alt="Use S3 pre-signed URL to upload documents"/>
</div>
- Now, let's go the final query **copySignedURL** that is connected to the table's action button. This query copy's the generated **Signed URL for download** onto the **clipboard**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/copysigned.png" alt="Use S3 pre-signed URL to upload documents"/>
</div>
- Now that we have updated all the queries, and connected them through the event handlers. We can go ahead and pick a file from the file picker. Click on the file picker, select a file and then hit the **Upload file to S3** button.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/uses3presignedurl/uploadbutton.png" alt="Use S3 pre-signed URL to upload documents"/>
</div>
- Once the button is clicked, the **uploadToS3** will triggered along with the **runjs1** and **runjs2** queries in sequence since we added them in the event handlers.
- You can go to the table and click on the **Copy signed URL** action button on the table, this will trigger the **copySignedURL** query and will copy the URL on the clipboard. You can go to another tab and paste the URL to open the file on the browser.

View file

@ -8,7 +8,7 @@ Permissions allow you to create and share resources to easily ensure what level
Admins can invite **Users** to their workspaces and assign them to the **Groups** that have Permissions to access Apps, folders, or workspace variables.
:::info
See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn how to invite users to ToolJet.
See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to know more about managing users and groups on your workspace.
:::
## Role-Based Access Control (RBAC) Glossary
@ -18,4 +18,4 @@ See **[Manage Users and Groups](/docs/tutorial/manage-users-groups)** to learn h
- **All Users** - Contains all the users in your workspace. When **New Users** are invited they are added to this group by default.
- **Admins** - Contains all Admins in your workspace. Everyone added to this group will Permission to access all the ToolJet resources.
- **Apps, Folder, Workspace Variables -** Resources that Admins can set permissions on.
- **Permissions -** Create, Update and Delete.
- **Permissions -** Create, Update and Delete.

View file

@ -295,3 +295,9 @@ If this parameter is not specified then PostgREST refuses authentication request
:::info
Please make sure that DB_URI is given in the format `postgrest://[USERNAME]:[PASSWORD]@[HOST]:[PORT]/[DATABASE]`
:::
## User Session Expiry Time (Optional)
| variable | description |
| ---------------- | ----------------------------------------------- |
| USER_SESSION_EXPIRY | This variable controls the user session expiry time. By default, the session expires after 2 days. The variable expects the value in minutes. ex: USER_SESSION_EXPIRY = 120 which is 2 hours |

View file

@ -77,7 +77,7 @@ Similar to archiving a user's access, you can enable it again by clicking on **U
## Managing Groups
On ToolJet, Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**.
On ToolJet, Admins and Super Admins can create groups for users added in a workspace and grant them access to particular app(s) with specific permissions. To manage groups, just go to the **Workspace Settings** from the left-sidebar of the dashboard and click on the **Groups**.
<div style={{textAlign: 'center'}}>
@ -87,11 +87,16 @@ On ToolJet, Admins can create groups for users added in a workspace and grant th
### Group properties
Every group on ToolJet has three sections:
Every group on ToolJet has **four** sections:
- [Apps](#apps)
- [Users](#users)
- [Permissions](#permissions)
- [Datasources](#datasources)
#### Apps:
Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group.
Admins and Super Admins can add or remove any number of apps for a group of users. To add an app to a group, select an app from the dropdown and click on `Add` button next to it. You can also set app permissions such as `View` or `Edit` for the group. You can set different permissions for different apps in a group.
<div style={{textAlign: 'center'}}>
@ -101,7 +106,7 @@ Admins can add or remove any number of apps for a group of users. To add an app
#### Users:
Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it.
Admins and Super Admins can add or remove any numbers of users in a group. Just select a user from the dropdown and click on `Add` button to add it to a group. To delete a user from a group, click on `Delete` button next to it.
<div style={{textAlign: 'center'}}>
@ -111,16 +116,30 @@ Admins can add or remove any numbers of users in a group. Just select a user fro
#### Permissions:
Admins can set granular permission like creating/deleting apps or creating folder for a group of users.
Admins and Super Admins can set granular permission for the users added in that particular group, such as:
- **Create** and **Delete** Apps
- **Create**, **Update**, and **Delete** Folders
- **Create**, **Update**, and **Delete** [Workspace Variables](/docs/tutorial/workspace-variables)
- **Create** and **Delete** [Global Datasources](/docs/widgets/overview)
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/permissionsv2.png" alt="permissions" />
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/dspermission.png" alt="permissions" />
</div>
#### Datasources:
Only Admins and Super Admins can define what datasources can be **viewed** or **edited** by the users of that group.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/gdspermission.png" alt="permissions" />
</div>
:::tip
All the activities performed by any Admin or any user in a workspace is logged in `Audit logs` - including any activity related with managing users and groups.
All the activities performed by any Admin, Super Admin or any user in a workspace is logged in [Audit logs](/docs/Enterprise/audit_logs) - including any activity related with managing users and groups.
:::
### Predefined Groups

View file

@ -97,4 +97,23 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
| variable | Description |
| ----------- | ----------- |
| annotations | This variable is an array of objects, where each object represents an annotation added to an image. The object contains the following keys: type, x, y, width, height, text, and id |
| annotations.`type` | There are two types of annotations: Rectangle and Point |
| annotations.`x` | coordinates on x axis |
| annotations.`y` | coordinates on y axis |
| annotations.`width` | width of annotation |
| annotations.`height` | height of annotation |
| annotations.`text` | label selected for the annotation |
| annotations.`id` | unique id of the annotation (system generated) |
The values can be accessed dynamically using `{{components.boundedbox1.annotations[0].text}}` or `{{components.boundedbox1.annotations[1].width}}`
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the bounding box component.

View file

@ -92,4 +92,15 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| selected | If the "enable multiple selection" option is turned off, then the variable is an array of objects, and the first object holds the value of the selected button. However, if the "enable multiple selection" option is turned on, the variable type changes from an array to an object, and the selected button values are stored as a string within that object. The value can be accessed using `{{components.buttongroup1.selected[0]}}` or `{{components.buttongroup1.selected}}` |
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the button-group component.

View file

@ -93,4 +93,20 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
Following actions of button component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| click | You can regulate the click of a button via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.button1.click()` |
| setText | button's text can be controlled using component specific action from any of the event handler. You can also use RunJS query to execute component specific actions: `await components.button1.setText('New Button Text')` |
| disable | button can be disabled using the component specific action from any of the event handler. You can also use RunJS query to execute this action: `await components.button1.disable(true)` or `await components.button1.disable(false)` |
| visibility | button's visibility can be switched using the component specific action from any of the event handler. You can also use RunJS query to execute this action: `await components.button1.disable(true)` or `await components.button1.disable(false)` |
| loading | The loading state of the button can be set dynamically using the component specific actions from any of the event handler. You can also use this action from RunJS: `await components.button1.loading(true)` or `await components.button1.loading(false)` |

View file

@ -197,3 +197,16 @@ This format determines how the column header for each day in week view will be d
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| selectedEvent | This variable stores information about the event that has been chosen on the calendar component. This object comprises keys like **title**, **start**, **end**, **allDay**, and **color**, and they can be accessed dynamically through JS using the following syntax: `{{components.calendar1.selectedEvent.title}}` or `{{components.calendar1.selectedEvent.start}}` |
| selectedSlots | The variable selectedSlots contains the values of the slots chosen on the calendar component. This object comprises keys like **slots**, **start**, **end**, **resourceId**, and **action**, and they can be accessed dynamically through JS using the following syntax: {{components.calendar1.selectedSlots.slots[0]}} or {{components.calendar1.selectedSlots.end}}. |
| currentView | The currentView variable holds the type of view currently set on the calendar. The value updates when the user changes the view from the calendar header. Types of views supported: `month`, `week`, and `day`. The value can be accessed using `{{components.calendar1.currentView}}` |
| currentDate | The currentDate variable holds the current date data. The date returned by the variable is in the `MM-DD-YYYY HH:mm:ss A Z` format. The value can be accessed using `{{components.calendar1.currentDate}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the calendar component.

View file

@ -387,3 +387,12 @@ Toggle on or off to control the visibility of the widget. You can programmatical
### Disable
This is `off` by default, toggle `on` the switch to lock the widget and make it non-functional. You can also programmatically set the value by clicking on the `Fx` button next to it. If set to `{{true}}`, the widget will be locked and becomes non-functional. By default, its value is set to `{{false}}`.
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -101,8 +101,16 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Actions
## Exposed Variables
| Action | Description | Properties |
| ----------- | ----------- | ------------------ |
| `setChecked` | Set checkbox state. | pass status as parameter. ex: `components.checkbox1.setChecked(true)` |
| Variables | Description |
| ----------- | ----------- |
| value | This variable holds the boolean value `true` if the checkbox is checked and `false` if unchecked. You can access the value dynamically using JS: `{{components.checkbox1.value}}`|
## Component specific actions (CSA)
Following actions of checkbox component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| setChecked | You can change the status of the checkbox component using component specific action from within any event handler. Additionally, you have the option to trigger it from the RunJS query: `await components.checkbox1.setChecked(true)` or `await components.checkbox1.setChecked(false)` |

View file

@ -75,4 +75,13 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Circular progress bar widget uses [react-circular-progress](https://github.com/kevinsqi/react-circular-progressbar) package. Check the repo for further more details about properties and styles.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -206,4 +206,14 @@ Under the <b>General</b> accordion, you can set the value in the string format.
| ----------- | ----------- |
| Visibility | Toggle on or off to control the visibility of the widget. You can programmatically change its value by clicking on the `Fx` button next to it. If `{{false}}` the widget will not be visible after the app is deployed. By default, it's set to `{{true}}`. |
| Disable | This is `off` by default, toggle `on` the switch to lock the widget and make it non-functional. You can also programmatically set the value by clicking on the `Fx` button next to it. If set to `{{true}}`, the widget will be locked and becomes non-functional. By default, its value is set to `{{false}}`. |
| Border radius | Use this property to modify the border radius of the editor. The field expects only numerical value from `1` to `100`, default is `0`. |
| Border radius | Use this property to modify the border radius of the editor. The field expects only numerical value from `1` to `100`, default is `0`. |
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| value | This variable holds the value whenever the user inputs anything on the code-editor . You can access the value dynamically using JS: `{{components.codeeditor1.value}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -81,4 +81,20 @@ Any property having `Fx` button next to its field can be **programmatically conf
<img className="screenshot-full" src="/img/widgets/color-picker/colorpickerinspector.png" alt="ToolJet - Widget Reference - Color Picker" />
</div>
</div>
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| selectedColorHex | This variable gets updated with HEX color code whenever a user selects a color from the color picker. You can access the value dynamically using JS: `{{components.colorpicker1.selectedColorHex}}`|
| selectedColorRGB | This variable gets updated with RGB color code whenever a user selects a color from the color picker. You can access the value dynamically using JS: `{{components.colorpicker1.selectedColorRGB}}`|
| selectedColorRGBA | This variable gets updated with RGBA color code whenever a user selects a color from the color picker. You can access the value dynamically using JS: `{{components.colorpicker1.selectedColorRGBA}}`|
## Component specific actions (CSA)
Following actions of color picker component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| setColor | Set a color on the color component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.colorpicker1.setColor('#64A07A')` |

View file

@ -8,7 +8,17 @@ Containers are used to group widgets together. You can move the desired number o
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/container/container.gif" alt="ToolJet - Widget Reference - Container" />
<img className="screenshot-full" src="/img/widgets/container/container.png" alt="ToolJet - Widget Reference - Container" />
</div>
## Enabling vertical scroll on container
To enable the vertical scroll on the container, drag and place any component to the bottom grid of the container and the container will automatically enable the scrolling.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/container/scrollcontainer.png" alt="ToolJet - Widget Reference - Container" />
</div>
@ -57,4 +67,13 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -103,3 +103,14 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| data | This variable will hold the variables assigned inside the `code` for custom component. You can access the value dynamically using JS: `{{components.customcomponent1.data.title}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -78,4 +78,16 @@ This property only accepts boolean values. If set to `{{true}}`, the widget will
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
| Variables | Description |
| ----------- | ----------- |
| endDate | This variable will hold the date of the endDate selected in the component. You can access the value dynamically using JS: `{{components.customcomponent1.data.title}}`|
| startDate | This variable will hold the value assigned inside the `code` for custom component. You can access the value dynamically using JS: `{{components.customcomponent1.data.title}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -106,3 +106,13 @@ Use this property to modify the border radius of the date-picker. The field expe
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed variables
| Variables | Description |
| ----------- | ----------- |
| value | This variable will hold the date selected on the component, the date value will be returned according to the format set in the datepicker properties. You can access the value dynamically using JS: `{{components.datepicker1.value}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -53,4 +53,13 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -137,4 +137,10 @@ Any property having `Fx` button next to its field can be **programmatically conf
| 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 |
| 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 |
## Component specific actions (CSA)
| Actions | Description |
| -------- | ----------- |
| selectOption | You can set an option on the dropdown component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.dropdown1.setOption(1)` |

View file

@ -165,8 +165,14 @@ Use this property to modify the border radius of the filepicker widget. The fiel
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Actions
## Exposed Variables
| Action | Description | Properties |
| ----------- | ----------- | ------------ |
| `clearFiles()` | It will clear the selected files | None |
| Variables | Description |
| ----------- | ----------- |
| file | This variable holds the array of objects where each object represents the file loaded on the file picker component. Each object has the following keys: **name**, **type**, **content**, **dataURL**, **base64Data**, **parsedData**, **filePath**. The values can be accesed using `{{components.filepicker1.file[0].base64Data}}` |
## Component specific actions (CSA)
| Actions | Description |
| -------- | ----------- |
| clearFiles() | You can clear the selected files on the filepicker component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.filepicker1.clearFiles()` |

View file

@ -73,4 +73,21 @@ A Tooltip is often used to specify extra information about something when the us
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| data | This variable holds the data of all the components that are nested inside the form components. You can access the value dynamically using JS: `{{components.form1.data.numberinput1.value}}`|
## Component specific actions (CSA)
Following actions of form component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| resetForm | You can submit the form data via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.form1.resetForm()` |
| submitForm | You can reset the form data via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await await components.form1.submitForm()` |

View file

@ -70,3 +70,11 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -62,3 +62,16 @@ Check [Action Reference](/docs/category/actions-reference) docs to get the detai
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
Following actions of the component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| setVisibility | You can toggle the visibility of the icon component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.icon1.setVisibility(false)` |
| click | You can trigger the click action on icon component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.icon1.click()` |

View file

@ -64,4 +64,12 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -104,4 +104,12 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -134,4 +134,15 @@ Under the <b>General</b> accordion, you can set the value in the string format.
| 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}}` |
| 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}}` |
## Component specific actions (CSA)
Following actions of kanban component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| updateCardData | Update the card data of kanban component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `components.kanban1.updateCardData('c1', {title: 'New Title'})` |
| moveCard | Move a card from one column to other column on the kanban via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.kanban1.moveCard('card id,'column id')` ex: `await components.kanban1.moveCard('c1','r2')` |
| addCard | Add a card onto the kanban via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.kanban1.addCard('c1', {title: 'New Title'})` |
| deleteCard | Delete a card from the kanban via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.kanban1.deleteCard('card id')` ex: `await components.kanban1.deleteCard('c2')` |

View file

@ -65,4 +65,17 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
Following actions of link component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| click | You can trigger the click action of the link component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.link1.click()` |

View file

@ -137,4 +137,14 @@ Any property having `Fx` button next to its field can be **programmatically conf
Use `{{listItem.key}}` to display data on the nested widgets. Example: For displaying the images we used `{{listItem.avatar}}` where **avatar** is one of the key in the objects from the query result.
:::
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| data | This variable holds the data loaded onto the listview component. You can access the data of each row of the listview using `{{components.listview1.data["0"].text1.text}}` |
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -80,8 +80,21 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Actions
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| center | This variable will hold the latitude, longitude and the google map url value. |
| center.`lat` | This variable holds the latitude value of the marker on the map component. You can access the value dynamically using JS: `{{components.map1.center.lat}}`|
| centere.`lng` | This variable gets updated with RGB color code whenever a user selects a color from the color picker. You can access the value dynamically using JS: `{{components.map1.center.lng}}`|
| center.`googleMapUrl` | This variable holds the URL of the location where the center marker is placed on the map component. You can access the value dynamically using JS: `{{components.map1.center.googleMapUrl}}`|
| markers | The markers variable will hold the value only if `add new markers` is enabled from the map properties. Each marker is an object and will have `lat` and `lng` keys. Values can be accessed dynamically using `{{components.map1.markers[1].lat}}` |
## Component specific actions (CSA)
Following actions of map component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| setLocation | Set the marker's location on map using latitude and longitude values as parameteres via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as: `component.map1.setLocation(40.7128, -73.935242)` |
| Action | Description | Properties |
| ----------- | ----------- | ------------------ |
| `setLocation` | Set map's location. | Latitude and Longitude values as parameters. ex: `component.map1.setLocation(40.7128, -73.935242)` |

View file

@ -98,4 +98,18 @@ Toggle on or off to display the widget in mobile view. You can programmatically
:::info
Trigger Button styles are only visible when **Use default trigger button** under Options is toggled on.
:::
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
Following actions of modal component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| open | Control the opening and closing of the modal componennt via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.modal1.open()` |
| close | Control the closing of the modal componennt via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.modal1.close()` |

View file

@ -87,3 +87,21 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| values | This variable holds the values of the multiselect component in an array of objects where the objects are the options in the multiselect. You can access the value dynamically using JS: `{{components.multiselect1.values[1]}}` |
## Component specific actions (CSA)
await components.multiselect1.clearSelections()
await components.multiselect1.deselectOption(2)
Following actions of multselect component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| selectOption | Select an option on the multiselect component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.multiselect1.selectOption(3)` |
| deselectOption | Deselect a selected option on the multiselect component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.multiselect1.deselectOption(3)` |
| clearOptions | Clear all the selected options from the multiselect component via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as `await components.multiselect1.clearSelections(2,3)` |

View file

@ -94,4 +94,14 @@ Change the color of the number in number-input component by entering the Hex col
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
| Variables | Description |
| ----------- | ----------- |
| value | This variable updates whenever a user selects a number on the number input. You can access the value dynamically using JS: `{{components.numberinput1.value}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -67,3 +67,13 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| totalPages | This variable holds the value of the `Number of Pages` set from the pagination component properties. You can access the value dynamically using JS: `{{components.pagination1.totalPages}}`|
| currentPageIndex | This variable will hold the index of the currently selected option on the pagination component. You can access the value dynamically using JS: `{{components.pagination1.currentPageIndex}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -79,4 +79,14 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
:::
## Exposed variables
| Variables | Description |
| ----------- | ----------- |
| value | This variable holds the value entered by the user onto the password input component. You can access the value dynamically using JS: `{{components.passwordinput1.value}}`|
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -54,3 +54,12 @@ Under the <b>General</b> accordion, you can set the value in the string format.
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed variables
There are currently no exposed variables for the component.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -69,3 +69,7 @@ Toggle on or off to control the visibility of the widget. You can programmatical
### Disable
This is `off` by default, toggle `on` the switch to lock the widget and make it non-functional. You can also programmatically set the value by clicking on the `Fx` button next to it. If set to `{{true}}`, the widget will be locked and becomes non-functional. By default, its value is set to `{{false}}`.
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

View file

@ -94,8 +94,14 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
</div>
## Actions
## Exposed variables
| Action | Description | Properties |
| ----------- | ----------- | ------------------ |
| selectOption | Select an option from the radio buttons. | `option` eg: `component.radiobutton1.selectOption('one')` |
There are currently no exposed variables for the component.
## Component specific actions (CSA)
Following actions of color picker component can be controlled using the component specific actions(CSA):
| Actions | Description |
| ----------- | ----------- |
| selectOption | Select an option from the radio buttons via a component-specific action within any event handler. Additionally, you have the option to employ a RunJS query to execute component-specific actions such as: `await components.radiobutton1.selectOption('one')` |

View file

@ -74,3 +74,13 @@ Set the visivlity of the slider programmatically. The default value is `{{true}}
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.
:::
## Exposed Variables
| Variables | Description |
| ----------- | ----------- |
| value | This variable holds an object when `two handles` option is disabled or an array when `two handles` is enabled from the component properties. The value can be accessed dynamically using JS: `{{components.rangeslider1.value}}` or `{{components.rangeslider1.value[1]}}` |
## Component specific actions (CSA)
There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component.

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