diff --git a/.env.example b/.env.example index 087f4d2596..d42467068f 100644 --- a/.env.example +++ b/.env.example @@ -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= diff --git a/.eslintignore b/.eslintignore index a8603104af..ed12b5b637 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ node_modules/** +cypress-tests/** \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eee484346..aa39961383 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index ca0bdb0490..4ef8ffbd6d 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -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 diff --git a/.github/workflows/packer-build.yml b/.github/workflows/packer-build.yml index 5504214a02..8996aec316 100644 --- a/.github/workflows/packer-build.yml +++ b/.github/workflows/packer-build.yml @@ -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 }} diff --git a/.version b/.version index 3f5987a5cb..437459cd94 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.4.9 +2.5.0 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..cebe515c1b --- /dev/null +++ b/SECURITY.md @@ -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`. diff --git a/cypress-tests/cypress-run.config.js b/cypress-tests/cypress-run.config.js index 5320e3e678..459c46cda5 100644 --- a/cypress-tests/cypress-run.config.js +++ b/cypress-tests/cypress-run.config.js @@ -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, diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 0347c4f20f..5e74123f5f 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -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, diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js index dd1689be43..5d21276d48 100644 --- a/cypress-tests/cypress/constants/selectors/common.js +++ b/cypress-tests/cypress/constants/selectors/common.js @@ -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 = { diff --git a/cypress-tests/cypress/constants/selectors/dashboard.js b/cypress-tests/cypress/constants/selectors/dashboard.js index 844e8374ea..81cfc4e7fc 100644 --- a/cypress-tests/cypress/constants/selectors/dashboard.js +++ b/cypress-tests/cypress/constants/selectors/dashboard.js @@ -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"]`; }, diff --git a/cypress-tests/cypress/constants/selectors/exportImport.js b/cypress-tests/cypress/constants/selectors/exportImport.js index 0313e34d5b..5de0135a1c 100644 --- a/cypress-tests/cypress/constants/selectors/exportImport.js +++ b/cypress-tests/cypress/constants/selectors/exportImport.js @@ -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"]', diff --git a/cypress-tests/cypress/constants/selectors/manageGroups.js b/cypress-tests/cypress/constants/selectors/manageGroups.js index 5b75ad007f..f668b8858e 100644 --- a/cypress-tests/cypress/constants/selectors/manageGroups.js +++ b/cypress-tests/cypress/constants/selectors/manageGroups.js @@ -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"]' }; diff --git a/cypress-tests/cypress/constants/selectors/manageSSO.js b/cypress-tests/cypress/constants/selectors/manageSSO.js index 0a650c9381..92e4d52655 100644 --- a/cypress-tests/cypress/constants/selectors/manageSSO.js +++ b/cypress-tests/cypress/constants/selectors/manageSSO.js @@ -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"]', }; \ No newline at end of file diff --git a/cypress-tests/cypress/constants/selectors/manageUsers.js b/cypress-tests/cypress/constants/selectors/manageUsers.js index 277bed2159..de980f279d 100644 --- a/cypress-tests/cypress/constants/selectors/manageUsers.js +++ b/cypress-tests/cypress/constants/selectors/manageUsers.js @@ -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"]', }; diff --git a/cypress-tests/cypress/constants/selectors/table.js b/cypress-tests/cypress/constants/selectors/table.js index bf92ddd87a..c169ca4ebc 100644 --- a/cypress-tests/cypress/constants/selectors/table.js +++ b/cypress-tests/cypress/constants/selectors/table.js @@ -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"]', diff --git a/cypress-tests/cypress/constants/texts/common.js b/cypress-tests/cypress/constants/texts/common.js index c0e0c08d8d..aa05dfde22 100644 --- a/cypress-tests/cypress/constants/texts/common.js +++ b/cypress-tests/cypress/constants/texts/common.js @@ -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 `We’ve sent an email to ${email} with a verification link. Please use that to verify your email address.`; }, diff --git a/cypress-tests/cypress/constants/texts/manageSSO.js b/cypress-tests/cypress/constants/texts/manageSSO.js index 55a5228d67..316bd782ff 100644 --- a/cypress-tests/cypress/constants/texts/manageSSO.js +++ b/cypress-tests/cypress/constants/texts/manageSSO.js @@ -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.', }; \ No newline at end of file diff --git a/cypress-tests/cypress/constants/texts/manageUsers.js b/cypress-tests/cypress/constants/texts/manageUsers.js index 979bb9ff6a..390a2d3750 100644 --- a/cypress-tests/cypress/constants/texts/manageUsers.js +++ b/cypress-tests/cypress/constants/texts/manageUsers.js @@ -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 won’t be able to recognise files in any other format. ', + helperTextSelectFile:'Select a CSV file to upload', + helperTextDropFile: 'Or drag and drop it here', }; diff --git a/cypress-tests/cypress/constants/texts/multipage.js b/cypress-tests/cypress/constants/texts/multipage.js index ecf0af3054..f827047c61 100644 --- a/cypress-tests/cypress/constants/texts/multipage.js +++ b/cypress-tests/cypress/constants/texts/multipage.js @@ -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", - -} \ No newline at end of file + labelNoPagesFound: "No pages found", +}; diff --git a/cypress-tests/cypress/e2e/authentication/forgotPassword.cy.js b/cypress-tests/cypress/e2e/authentication/forgotPassword.cy.js index 5ba8a3fe3b..63ebf1953f 100644 --- a/cypress-tests/cypress/e2e/authentication/forgotPassword.cy.js +++ b/cypress-tests/cypress/e2e/authentication/forgotPassword.cy.js @@ -13,7 +13,7 @@ describe("Password reset functionality", () => { before(() => { cy.appUILogin(); - addNewUserMW(data.firstName, data.lastName, data.email); + addNewUserMW(data.firstName, data.email); logout(); }); diff --git a/cypress-tests/cypress/e2e/authentication/signUp.cy.js b/cypress-tests/cypress/e2e/authentication/signUp.cy.js index e02f59d8c3..24a5f24ef2 100644 --- a/cypress-tests/cypress/e2e/authentication/signUp.cy.js +++ b/cypress-tests/cypress/e2e/authentication/signUp.cy.js @@ -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); diff --git a/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js b/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js index df16d00ced..ffecf5bbad 100644 --- a/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js +++ b/cypress-tests/cypress/e2e/dashboard/dashboard.cy.js @@ -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); diff --git a/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageSSO.cy.js b/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageSSO.cy.js index 87f20512dc..cfbeb13b7c 100644 --- a/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageSSO.cy.js +++ b/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageSSO.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js b/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js index f1c90a6763..f854d59dd4 100644 --- a/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js +++ b/cypress-tests/cypress/e2e/dashboard/multi-workspace/manageUsers.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/dashboard/multi-workspace/userPermissions.cy.js b/cypress-tests/cypress/e2e/dashboard/multi-workspace/userPermissions.cy.js index aee3df0eff..5afd66dbf0 100644 --- a/cypress-tests/cypress/e2e/dashboard/multi-workspace/userPermissions.cy.js +++ b/cypress-tests/cypress/e2e/dashboard/multi-workspace/userPermissions.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/dashboard/single-workspace/manageSSO.cy.js b/cypress-tests/cypress/e2e/dashboard/single-workspace/manageSSO.cy.js index bfe67897e9..a2af6e2bae 100644 --- a/cypress-tests/cypress/e2e/dashboard/single-workspace/manageSSO.cy.js +++ b/cypress-tests/cypress/e2e/dashboard/single-workspace/manageSSO.cy.js @@ -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 diff --git a/cypress-tests/cypress/e2e/editor/app-version/version.cy.js b/cypress-tests/cypress/e2e/editor/app-version/version.cy.js index d002a86f90..0d48eda0ac 100644 --- a/cypress-tests/cypress/e2e/editor/app-version/version.cy.js +++ b/cypress-tests/cypress/e2e/editor/app-version/version.cy.js @@ -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"); diff --git a/cypress-tests/cypress/e2e/editor/multipage/multipathHappypath.cy.js b/cypress-tests/cypress/e2e/editor/multipage/multipageHappypath.cy.js similarity index 94% rename from cypress-tests/cypress/e2e/editor/multipage/multipathHappypath.cy.js rename to cypress-tests/cypress/e2e/editor/multipage/multipageHappypath.cy.js index ac4e0bebfb..0a78b6a598 100644 --- a/cypress-tests/cypress/e2e/editor/multipage/multipathHappypath.cy.js +++ b/cypress-tests/cypress/e2e/editor/multipage/multipageHappypath.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js b/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js index 4ae5c44a53..52238d0333 100644 --- a/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/componentsBasicHappypath.cy.js @@ -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 + ); + }); }); diff --git a/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js index 9a144180d2..843b8b90ff 100644 --- a/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/datePickerHappyPath.cy.js @@ -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); diff --git a/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js index fb99e3a586..b7c7117b4a 100644 --- a/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/listViewHappyPath.cy.js @@ -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"); diff --git a/cypress-tests/cypress/e2e/editor/widget/modalHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/modalHappyPath.cy.js new file mode 100644 index 0000000000..6f89b9a3e4 --- /dev/null +++ b/cypress-tests/cypress/e2e/editor/widget/modalHappyPath.cy.js @@ -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"]'); + }); +}); diff --git a/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js index a718bfa249..9615a8fe1d 100644 --- a/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/multiselectHappyPath.cy.js @@ -123,8 +123,7 @@ describe("Multiselect widget", () => { cy.get(multiselectSelector.dropdownAllItems) .first() .should("have.text", multiselectText.dropdwonOptionSelectAll) - .click() - .click(); + .realClick(); verifyMultiselectHeader( data.widgetName, diff --git a/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js b/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js index 1d7867394b..f6873cd954 100644 --- a/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/numberInputHappyPath.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js b/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js index 65a73036f5..ec5d8ce99c 100644 --- a/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js +++ b/cypress-tests/cypress/e2e/editor/widget/tableRegression.cy.js @@ -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); diff --git a/cypress-tests/cypress/e2e/exportImport/export.cy.js b/cypress-tests/cypress/e2e/exportImport/export.cy.js index 13d3adf858..4e936b3054 100644 --- a/cypress-tests/cypress/e2e/exportImport/export.cy.js +++ b/cypress-tests/cypress/e2e/exportImport/export.cy.js @@ -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") diff --git a/cypress-tests/cypress/e2e/exportImport/import.cy.js b/cypress-tests/cypress/e2e/exportImport/import.cy.js index 2a0503f587..f4c0b688cc 100644 --- a/cypress-tests/cypress/e2e/exportImport/import.cy.js +++ b/cypress-tests/cypress/e2e/exportImport/import.cy.js @@ -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(); diff --git a/cypress-tests/cypress/e2e/selfHost/selfHostSignUp.cy.js b/cypress-tests/cypress/e2e/selfHost/selfHostSignUp.cy.js index e54eef448f..5f73a64877 100644 --- a/cypress-tests/cypress/e2e/selfHost/selfHostSignUp.cy.js +++ b/cypress-tests/cypress/e2e/selfHost/selfHostSignUp.cy.js @@ -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(); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 2499e92441..66c6c6fa08 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -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) => { diff --git a/cypress-tests/cypress/support/utils/basicComponents.js b/cypress-tests/cypress/support/utils/basicComponents.js index 4bfbbf28d2..a9045b0721 100644 --- a/cypress-tests/cypress/support/utils/basicComponents.js +++ b/cypress-tests/cypress/support/utils/basicComponents.js @@ -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); -} - - - - - +}; diff --git a/cypress-tests/cypress/support/utils/common.js b/cypress-tests/cypress/support/utils/common.js index a3648bee04..a881932ebb 100644 --- a/cypress-tests/cypress/support/utils/common.js +++ b/cypress-tests/cypress/support/utils/common.js @@ -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) => { diff --git a/cypress-tests/cypress/support/utils/commonWidget.js b/cypress-tests/cypress/support/utils/commonWidget.js index 8d9af176af..237d2c034d 100644 --- a/cypress-tests/cypress/support/utils/commonWidget.js +++ b/cypress-tests/cypress/support/utils/commonWidget.js @@ -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})` diff --git a/cypress-tests/cypress/support/utils/dashboard.js b/cypress-tests/cypress/support/utils/dashboard.js index 0049064e0f..a76ae4d926 100644 --- a/cypress-tests/cypress/support/utils/dashboard.js +++ b/cypress-tests/cypress/support/utils/dashboard.js @@ -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"); }; diff --git a/cypress-tests/cypress/support/utils/database.js b/cypress-tests/cypress/support/utils/database.js index 626c84218a..60cab3fa30 100644 --- a/cypress-tests/cypress/support/utils/database.js +++ b/cypress-tests/cypress/support/utils/database.js @@ -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++) { diff --git a/cypress-tests/cypress/support/utils/events.js b/cypress-tests/cypress/support/utils/events.js new file mode 100644 index 0000000000..4eb537a327 --- /dev/null +++ b/cypress-tests/cypress/support/utils/events.js @@ -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}`); +}; diff --git a/cypress-tests/cypress/support/utils/exportImport.js b/cypress-tests/cypress/support/utils/exportImport.js index cb6a870544..3adb27da2a 100644 --- a/cypress-tests/cypress/support/utils/exportImport.js +++ b/cypress-tests/cypress/support/utils/exportImport.js @@ -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()); diff --git a/cypress-tests/cypress/support/utils/listviewWidget.js b/cypress-tests/cypress/support/utils/listviewWidget.js index feca37df26..925db7186a 100644 --- a/cypress-tests/cypress/support/utils/listviewWidget.js +++ b/cypress-tests/cypress/support/utils/listviewWidget.js @@ -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"); }); }; diff --git a/cypress-tests/cypress/support/utils/manageSSO.js b/cypress-tests/cypress/support/utils/manageSSO.js index 876dfb132a..c56cd2219a 100644 --- a/cypress-tests/cypress/support/utils/manageSSO.js +++ b/cypress-tests/cypress/support/utils/manageSSO.js @@ -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(); } }); diff --git a/cypress-tests/cypress/support/utils/manageUsers.js b/cypress-tests/cypress/support/utils/manageUsers.js index 0daf568eca..0251b70afd 100644 --- a/cypress-tests/cypress/support/utils/manageUsers.js +++ b/cypress-tests/cypress/support/utils/manageUsers.js @@ -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 diff --git a/cypress-tests/cypress/support/utils/modal.js b/cypress-tests/cypress/support/utils/modal.js new file mode 100644 index 0000000000..75df99ccbe --- /dev/null +++ b/cypress-tests/cypress/support/utils/modal.js @@ -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 + ); +}; diff --git a/cypress-tests/cypress/support/utils/multipage.js b/cypress-tests/cypress/support/utils/multipage.js index 81f3b85f6e..99bd4a3f74 100644 --- a/cypress-tests/cypress/support/utils/multipage.js +++ b/cypress-tests/cypress/support/utils/multipage.js @@ -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(); }; diff --git a/cypress-tests/cypress/support/utils/table.js b/cypress-tests/cypress/support/utils/table.js index db6991c584..2ddf4ff6a1 100644 --- a/cypress-tests/cypress/support/utils/table.js +++ b/cypress-tests/cypress/support/utils/table.js @@ -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(); +}; diff --git a/cypress-tests/cypress/support/utils/userPermissions.js b/cypress-tests/cypress/support/utils/userPermissions.js index 990f8047c5..7d2e321cc4 100644 --- a/cypress-tests/cypress/support/utils/userPermissions.js +++ b/cypress-tests/cypress/support/utils/userPermissions.js @@ -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( diff --git a/cypress-tests/cypress/support/utils/version.js b/cypress-tests/cypress/support/utils/version.js index 4b1fc5a3bd..c1bfdb580c 100644 --- a/cypress-tests/cypress/support/utils/version.js +++ b/cypress-tests/cypress/support/utils/version.js @@ -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); }; diff --git a/docs/docs/Enterprise/superadmin.md b/docs/docs/Enterprise/superadmin.md index 63262b98e9..0af8d92a66 100644 --- a/docs/docs/Enterprise/superadmin.md +++ b/docs/docs/Enterprise/superadmin.md @@ -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) | ❌ | ✅ | diff --git a/docs/docs/contributing-guide/troubleshooting/eslint.md b/docs/docs/contributing-guide/troubleshooting/eslint.md index 465ba553c3..efb8d897fe 100644 --- a/docs/docs/contributing-guide/troubleshooting/eslint.md +++ b/docs/docs/contributing-guide/troubleshooting/eslint.md @@ -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: {...}**. +::: \ No newline at end of file diff --git a/docs/docs/data-sources/grpc.md b/docs/docs/data-sources/grpc.md new file mode 100644 index 0000000000..5afb81d526 --- /dev/null +++ b/docs/docs/data-sources/grpc.md @@ -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 \ No newline at end of file diff --git a/docs/docs/data-sources/overview.md b/docs/docs/data-sources/overview.md index ec7527f8af..64eefc507c 100644 --- a/docs/docs/data-sources/overview.md +++ b/docs/docs/data-sources/overview.md @@ -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**. +:::
@@ -13,30 +17,115 @@ Datasources pull in and push data to any source including databases, external AP
-## 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. +
-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 + Datasources: Overview -
+
- Datasources: Overview +2. Click on the **Add new datasource** button, a modal will pop-up with all the available global datasources. +
-
+ Datasources: Overview - 2. **Query Panel**: Go to the query panel at the bottom, click on the `+Add` button and then click `Add datasource` button +
-
+3. Select the datasource, enter the **Credentials** and **Save** the datasource. +
- Datasources: Overview + Datasources: Overview -
+
-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**. +
+ + Datasources: Overview + +
+ +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. +
+ + Datasources: Overview + +
+ +## 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. +
+ + Datasources: Overview + +
+ +2. Click on the kebab menu next to the connected datasource, select the **change scope** option. +
+ + Datasources: Overview + +
+ +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**. +
+ + Datasources: Overview + +
+ + +## 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/)** + +
+ + Datasources: Overview + +
+ +## 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. +
+ + Datasources: Overview + +
+ + - 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. +
+ + Datasources: Overview + +
+ +- **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. +
+ + Datasources: Overview + +
+ + - 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. +
+ + Datasources: Overview + +
-:::info -ToolJet allows you to transform the data returned by datasources using **[Transformations](/docs/tutorial/transformations)** -::: diff --git a/docs/docs/data-sources/smtp.md b/docs/docs/data-sources/smtp.md index 120f2b1d08..9f09a14ac4 100644 --- a/docs/docs/data-sources/smtp.md +++ b/docs/docs/data-sources/smtp.md @@ -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. :::
@@ -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. -smtp query1 +
+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. - - -smtp query2 +
\ No newline at end of file diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 3d944f62c9..4e83aaca55 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -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**.
@@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::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
:::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 :::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 diff --git a/docs/docs/how-to/intentionally-fail-js-query.md b/docs/docs/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/docs/how-to/intentionally-fail-js-query.md @@ -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. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/docs/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/docs/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/docs/how-to/use-s3-presigned-url-to-upload-docs.md @@ -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. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- 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. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- 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 + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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 + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - 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": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. + diff --git a/docs/docs/org-management/permissions.md b/docs/docs/org-management/permissions.md index 5e33f5cb2c..b13433e0ef 100644 --- a/docs/docs/org-management/permissions.md +++ b/docs/docs/org-management/permissions.md @@ -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. \ No newline at end of file diff --git a/docs/docs/setup/env-vars.md b/docs/docs/setup/env-vars.md index ee546a8cbb..30782c3337 100644 --- a/docs/docs/setup/env-vars.md +++ b/docs/docs/setup/env-vars.md @@ -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 | \ No newline at end of file diff --git a/docs/docs/tutorial/manage-users-groups.md b/docs/docs/tutorial/manage-users-groups.md index 95991fd7fa..185d078f60 100644 --- a/docs/docs/tutorial/manage-users-groups.md +++ b/docs/docs/tutorial/manage-users-groups.md @@ -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**.
@@ -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.
@@ -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.
@@ -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)
-permissions +permissions + +
+ +#### Datasources: + +Only Admins and Super Admins can define what datasources can be **viewed** or **edited** by the users of that group. + +
+ +permissions
:::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 diff --git a/docs/docs/widgets/bounded-box.md b/docs/docs/widgets/bounded-box.md index 4a7edb33be..855f65cb40 100644 --- a/docs/docs/widgets/bounded-box.md +++ b/docs/docs/widgets/bounded-box.md @@ -97,4 +97,23 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/docs/widgets/button-group.md b/docs/docs/widgets/button-group.md index f415e830c7..09c5821663 100644 --- a/docs/docs/widgets/button-group.md +++ b/docs/docs/widgets/button-group.md @@ -92,4 +92,15 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/docs/widgets/button.md b/docs/docs/widgets/button.md index dd86c3b615..0d64c4bd6e 100644 --- a/docs/docs/widgets/button.md +++ b/docs/docs/widgets/button.md @@ -93,4 +93,20 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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)` | \ No newline at end of file diff --git a/docs/docs/widgets/calendar.md b/docs/docs/widgets/calendar.md index 454a2393da..27791aea3f 100644 --- a/docs/docs/widgets/calendar.md +++ b/docs/docs/widgets/calendar.md @@ -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. \ No newline at end of file diff --git a/docs/docs/widgets/chart.md b/docs/docs/widgets/chart.md index 9760de273f..c3e67fea1e 100644 --- a/docs/docs/widgets/chart.md +++ b/docs/docs/widgets/chart.md @@ -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. diff --git a/docs/docs/widgets/checkbox.md b/docs/docs/widgets/checkbox.md index dbdc0f0650..9e6639d508 100644 --- a/docs/docs/widgets/checkbox.md +++ b/docs/docs/widgets/checkbox.md @@ -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)` | \ No newline at end of file +| 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)` | \ No newline at end of file diff --git a/docs/docs/widgets/circular-progressbar.md b/docs/docs/widgets/circular-progressbar.md index 47064de88e..1914c9f6f8 100644 --- a/docs/docs/widgets/circular-progressbar.md +++ b/docs/docs/widgets/circular-progressbar.md @@ -75,4 +75,13 @@ Under the General 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. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/docs/widgets/code-editor.md b/docs/docs/widgets/code-editor.md index 612593221e..6434cd0e3f 100644 --- a/docs/docs/widgets/code-editor.md +++ b/docs/docs/widgets/code-editor.md @@ -206,4 +206,14 @@ Under the General 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`. | \ No newline at end of file +| 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. diff --git a/docs/docs/widgets/color-picker.md b/docs/docs/widgets/color-picker.md index 36a8fc6658..2c3260315c 100644 --- a/docs/docs/widgets/color-picker.md +++ b/docs/docs/widgets/color-picker.md @@ -81,4 +81,20 @@ Any property having `Fx` button next to its field can be **programmatically conf ToolJet - Widget Reference - Color Picker -
\ No newline at end of file +
+ +## 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')` | \ No newline at end of file diff --git a/docs/docs/widgets/container.md b/docs/docs/widgets/container.md index fc58141f51..b2c78a4f37 100644 --- a/docs/docs/widgets/container.md +++ b/docs/docs/widgets/container.md @@ -8,7 +8,17 @@ Containers are used to group widgets together. You can move the desired number o
-ToolJet - Widget Reference - Container +ToolJet - Widget Reference - Container + +
+ +## 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. + +
+ +ToolJet - Widget Reference - Container
@@ -57,4 +67,13 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## 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. \ No newline at end of file diff --git a/docs/docs/widgets/custom-component.md b/docs/docs/widgets/custom-component.md index 385252605d..953e8dcd83 100644 --- a/docs/docs/widgets/custom-component.md +++ b/docs/docs/widgets/custom-component.md @@ -103,3 +103,14 @@ Under the General 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. diff --git a/docs/docs/widgets/date-range-picker.md b/docs/docs/widgets/date-range-picker.md index 642c97b427..0b27cf4bca 100644 --- a/docs/docs/widgets/date-range-picker.md +++ b/docs/docs/widgets/date-range-picker.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/docs/widgets/datepicker.md b/docs/docs/widgets/datepicker.md index 19001e9fb6..22f19e4955 100644 --- a/docs/docs/widgets/datepicker.md +++ b/docs/docs/widgets/datepicker.md @@ -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. \ No newline at end of file diff --git a/docs/docs/widgets/divider.md b/docs/docs/widgets/divider.md index b027ae96d2..469b6626d0 100644 --- a/docs/docs/widgets/divider.md +++ b/docs/docs/widgets/divider.md @@ -53,4 +53,13 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/docs/widgets/dropdown.md b/docs/docs/widgets/dropdown.md index e4cdca4a72..9bd7ee5a9b 100644 --- a/docs/docs/widgets/dropdown.md +++ b/docs/docs/widgets/dropdown.md @@ -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 | \ No newline at end of file +| 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)` | \ No newline at end of file diff --git a/docs/docs/widgets/file-picker.md b/docs/docs/widgets/file-picker.md index 6d7efd4219..7805240bb3 100644 --- a/docs/docs/widgets/file-picker.md +++ b/docs/docs/widgets/file-picker.md @@ -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()` | diff --git a/docs/docs/widgets/form.md b/docs/docs/widgets/form.md index 8d368d65fd..ba5f9e5e70 100644 --- a/docs/docs/widgets/form.md +++ b/docs/docs/widgets/form.md @@ -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**. -::: \ No newline at end of file +::: + + +## 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()` | + diff --git a/docs/docs/widgets/html.md b/docs/docs/widgets/html.md index cdb523f9d7..41908bef43 100644 --- a/docs/docs/widgets/html.md +++ b/docs/docs/widgets/html.md @@ -70,3 +70,11 @@ Under the General 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. diff --git a/docs/docs/widgets/icon.md b/docs/docs/widgets/icon.md index 9cd9fe3bd9..fd93553088 100644 --- a/docs/docs/widgets/icon.md +++ b/docs/docs/widgets/icon.md @@ -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()` | \ No newline at end of file diff --git a/docs/docs/widgets/iframe.md b/docs/docs/widgets/iframe.md index 3157b9f979..d73ac37210 100644 --- a/docs/docs/widgets/iframe.md +++ b/docs/docs/widgets/iframe.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/docs/widgets/image.md b/docs/docs/widgets/image.md index f84ee6f9c3..33a071bb82 100644 --- a/docs/docs/widgets/image.md +++ b/docs/docs/widgets/image.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/docs/widgets/kanban-board.md b/docs/docs/widgets/kanban-board.md index 0dbb693192..a77239b9e0 100644 --- a/docs/docs/widgets/kanban-board.md +++ b/docs/docs/widgets/kanban-board.md @@ -134,4 +134,15 @@ Under the General 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}}` | \ No newline at end of file +| 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')` | diff --git a/docs/docs/widgets/link.md b/docs/docs/widgets/link.md index ca20002102..fffd89239a 100644 --- a/docs/docs/widgets/link.md +++ b/docs/docs/widgets/link.md @@ -65,4 +65,17 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## 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()` | \ No newline at end of file diff --git a/docs/docs/widgets/listview.md b/docs/docs/widgets/listview.md index 41a8d42385..4ab9ddd6b0 100644 --- a/docs/docs/widgets/listview.md +++ b/docs/docs/widgets/listview.md @@ -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. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/docs/widgets/map.md b/docs/docs/widgets/map.md index 2469048e68..97b8764cc8 100644 --- a/docs/docs/widgets/map.md +++ b/docs/docs/widgets/map.md @@ -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)` | \ No newline at end of file diff --git a/docs/docs/widgets/modal.md b/docs/docs/widgets/modal.md index eb196123ac..02f645e79e 100644 --- a/docs/docs/widgets/modal.md +++ b/docs/docs/widgets/modal.md @@ -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. -::: \ No newline at end of file +::: + +## 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()` | \ No newline at end of file diff --git a/docs/docs/widgets/multiselect.md b/docs/docs/widgets/multiselect.md index cdc83f7ab9..8d3f15ee9f 100644 --- a/docs/docs/widgets/multiselect.md +++ b/docs/docs/widgets/multiselect.md @@ -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)` | \ No newline at end of file diff --git a/docs/docs/widgets/number-input.md b/docs/docs/widgets/number-input.md index f72242234c..115a1a37d1 100644 --- a/docs/docs/widgets/number-input.md +++ b/docs/docs/widgets/number-input.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/docs/widgets/pagination.md b/docs/docs/widgets/pagination.md index 2ee0003cc5..182e4211f2 100644 --- a/docs/docs/widgets/pagination.md +++ b/docs/docs/widgets/pagination.md @@ -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. \ No newline at end of file diff --git a/docs/docs/widgets/password-input.md b/docs/docs/widgets/password-input.md index 019ec8fae3..b3ec9f6ba6 100644 --- a/docs/docs/widgets/password-input.md +++ b/docs/docs/widgets/password-input.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/docs/widgets/pdf.md b/docs/docs/widgets/pdf.md index bc665697fe..6af77f190f 100644 --- a/docs/docs/widgets/pdf.md +++ b/docs/docs/widgets/pdf.md @@ -54,3 +54,12 @@ Under the General 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. diff --git a/docs/docs/widgets/qr-scanner.md b/docs/docs/widgets/qr-scanner.md index edab5ade23..f031728575 100644 --- a/docs/docs/widgets/qr-scanner.md +++ b/docs/docs/widgets/qr-scanner.md @@ -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. diff --git a/docs/docs/widgets/radio-button.md b/docs/docs/widgets/radio-button.md index 9de2d08c16..26c3c5c725 100644 --- a/docs/docs/widgets/radio-button.md +++ b/docs/docs/widgets/radio-button.md @@ -94,8 +94,14 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
-## Actions +## Exposed variables -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| selectOption | Select an option from the radio buttons. | `option` eg: `component.radiobutton1.selectOption('one')` | \ No newline at end of file +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')` | \ No newline at end of file diff --git a/docs/docs/widgets/range-slider.md b/docs/docs/widgets/range-slider.md index 5c58220496..604d9ee204 100644 --- a/docs/docs/widgets/range-slider.md +++ b/docs/docs/widgets/range-slider.md @@ -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. diff --git a/docs/docs/widgets/rich-text-editor.md b/docs/docs/widgets/rich-text-editor.md index 2267259807..7c5ba592d7 100644 --- a/docs/docs/widgets/rich-text-editor.md +++ b/docs/docs/widgets/rich-text-editor.md @@ -54,3 +54,13 @@ 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 + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever a user enters a value in the rich text editor component. You can access the value dynamically using JS: `{{components.richtexteditor1.value}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/docs/widgets/spinner.md b/docs/docs/widgets/spinner.md index 8557673342..7869b13be0 100644 --- a/docs/docs/widgets/spinner.md +++ b/docs/docs/widgets/spinner.md @@ -46,4 +46,12 @@ Change the color of the Spinner by entering the `Hex color code` or choosing a c ### Size -Change the size of the Spinner by selecting options from the dropdown. It has small and large sizes available. \ No newline at end of file +Change the size of the Spinner by selecting options from the dropdown. It has small and large sizes available. + +## 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. diff --git a/docs/docs/widgets/star-rating.md b/docs/docs/widgets/star-rating.md index d204438589..3b5d2cfe9e 100644 --- a/docs/docs/widgets/star-rating.md +++ b/docs/docs/widgets/star-rating.md @@ -83,3 +83,13 @@ 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 + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever a rating is added on the component. The variable holds a numerical value. You can access the value dynamically using JS: `{{components.colorpicker1.selectedColorHex}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/docs/widgets/statistics.md b/docs/docs/widgets/statistics.md index b2fa668f13..62cd3f8e2b 100644 --- a/docs/docs/widgets/statistics.md +++ b/docs/docs/widgets/statistics.md @@ -91,3 +91,11 @@ Toggle on or off to control the visibility of the widget. You can programmatical :::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. diff --git a/docs/docs/widgets/steps.md b/docs/docs/widgets/steps.md index 5c486e0ecb..bdd757dce9 100644 --- a/docs/docs/widgets/steps.md +++ b/docs/docs/widgets/steps.md @@ -89,4 +89,15 @@ Toggle on or off to control the visibility of the widget. You can programmatical :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| currentStepId | This variable holds the id of the currently selected step on the step component. You can access the value dynamically using JS: `{{components.steps1.currentStepId}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/docs/widgets/svg-image.md b/docs/docs/widgets/svg-image.md index 76500c1eb8..b3a7a5ac7f 100644 --- a/docs/docs/widgets/svg-image.md +++ b/docs/docs/widgets/svg-image.md @@ -56,4 +56,12 @@ Toggle on or off to display the widget in mobile view. You can programmatically ### 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}}`. \ No newline at end of file +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}}`. + +## 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. \ No newline at end of file diff --git a/docs/docs/widgets/table.md b/docs/docs/widgets/table.md index 15b05f6fbd..cd2b9ccae6 100644 --- a/docs/docs/widgets/table.md +++ b/docs/docs/widgets/table.md @@ -8,11 +8,64 @@ Tables can be used for both displaying and editing data. +## Table UI + +
+ +ToolJet - Widget Reference - Table + +
+ +### Search + +At the top-left corner of the table component, there is a search box that allows users to input keywords and search for rows within the table data. You can also **[show/hide the search box](/docs/widgets/table#show-search-box)** from the table from the table properties. + +### Add new row + +When users click on this button, a popup modal appears which enables them to insert new rows. The modal will have a single row initially, and the columns will have the same column type as those on the table. If the user inputs data into the row, it will be stored on the **[`newRows` variable](/docs/widgets/table#exposed-variables)** of the table. If the user selects the **Discard** button, the data in the variable will be cleared. However, if the user closes the popup without taking any action (neither Save nor Discard), the data will still be retained, and a green indicator will appear on the **Add new row** button. The table has an **[Add new rows event handler](/docs//widgets/table#add-new-rows)** that can be utilized to execute queries that store the data into the datasource whenever the **Save** button is clicked. + +:::info +At present, it is not possible to include columns of type Image when adding a new row to the table. +::: + +### Filters + +The table data can be filtered by clicking on this button. You have the option to choose from various filters, such as: + +- **contains** +- **does not contain** +- **matches** +- **does not match** +- **equals** +- **does not equal** +- **is empty** +- **is not empty** +- **greater than** +- **greater than or equal to** +- **less than** +- **less than or equal to** + +You have the option to **[hide the filter button](/docs/widgets/table#show-filter-button)** in the table properties. + +### Download + +The table data can be downloaded in various file formats, including: + +- **CSV** +- **Excel** +- **PDF** + +You have the option to **[hide the download button](/docs/widgets/table#show-download-button)** in the table properties. + +### Column selector button + +You can choose which columns to display or hide in the table by clicking on this button. You also have the option to **[hide the column selector button](/docs/widgets/table#show-column-selector-button)** in the table properties. + ## Table data
-ToolJet - Widget Reference - Table +ToolJet - Widget Reference - Table
@@ -197,28 +250,15 @@ If server-side search is enabled, `on search` event is fired after the content o ### Show download button -Show or hide download button at the Table footer. +The download button in the table header is visible by default. You can choose to hide it by disabling this option. You can dynamically set the value to {{true}} or {{false}} to show or hide the download button by clicking on the **Fx** button. -### Hide/Show columns +### Show column selector button -Table header has an option(Eye icon) to show/hide one or many columns on the table. +The column selector button on the table header is visible by default. You can choose to hide it by disabling this option. You can dynamically set the value to {{true}} or {{false}} to show or hide the column selector button by clicking on the **Fx** button. ### Show filter button -Show or hide filter button at the Table header. The following filters are available: -- **contains** -- **does not contain** -- **matches** -- **does not match** -- **equals** -- **does not equal to** -- **is empty** -- **is not empty** -- **greater than** -- **greater than or equal to** -- **less than** -- **less than or equal to** - +The filter button in the table header is visible by default. You can choose to hide it by disabling this option. You can dynamically set the value to {{true}} or {{false}} to show or hide the filter button by clicking on the **Fx** button. ### Show update buttons @@ -263,6 +303,7 @@ Loading state shows a loading skeleton for the table. This property can be used - **[Sort applied](#sort-applied)** - **[Cell value changed](#cell-value-changed)** - **[Filter changed](#filter-changed)** +- **[Add new rows](#add-new-rows)** ### Row hovered @@ -300,6 +341,10 @@ If any cell of the table is edited, the `cell value changed` event is triggered. This event is triggered when filter is added, removed, or updated from the filter section of the table. `filters` property of the table is updated to reflect the status of filters applied. The objects will have properties: `condition`, `value`, and `column`. +### Add new rows + +This event is triggered when the **Save** button is clicked from the **Add new row** modal on the table. + ## Exposed variables | variable | description | @@ -311,6 +356,7 @@ This event is triggered when filter is added, removed, or updated from the filte | dataUpdates | Just like changeSet but includes the data of the entire row | | selectedRow | The data of the row that was last clicked. `selectedRow` also changes when an action button is clicked | | searchText | The value of the search field if server-side pagination is enabled | +| newRows| The newRows variable stores an array of objects, each containing data for a row that was added to the table using the "Add new row" button. When the user clicks either the "Save" or "Discard" button in the modal, this data is cleared.| ## Styles @@ -327,3 +373,15 @@ This event is triggered when filter is added, removed, or updated from the filte :::info Any property having `Fx` button next to its field can be **programmatically configured**. ::: + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setPage | Set the page on the table via 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.table1.setPage(2)` | +| selectRow | Select the row on the table using via 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.table1.selectRow('id','11')` | +| deselectRow | Deselect the row on the table via 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.table1.deselectRow()` | +| discardChanges | Discard the changes from the table when a cell is edited via 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.table1.discardChanges()` | +| discardNewlyAddedRows | Discard the newly added rows from the add new row popup on the table via 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.table1.discardNewlyAddedRows()` | \ No newline at end of file diff --git a/docs/docs/widgets/tabs.md b/docs/docs/widgets/tabs.md index 0bfa2a8a2a..60930903ce 100644 --- a/docs/docs/widgets/tabs.md +++ b/docs/docs/widgets/tabs.md @@ -113,4 +113,18 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| currentTab | This variable holds the id of the current tab selected on the tabs component. You can access the value dynamically using JS: `{{components.tabs1.currentTab}}`| + +## Component specific actions (CSA) + +Following actions of Tabs component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setTab | Set the current tab of the tabs 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.tabs1.setTab(1)` | \ No newline at end of file diff --git a/docs/docs/widgets/tags.md b/docs/docs/widgets/tags.md index ea01906c69..8d86319d07 100644 --- a/docs/docs/widgets/tags.md +++ b/docs/docs/widgets/tags.md @@ -57,4 +57,13 @@ Toggle on or off to display the widget in mobile view. You can programmatically ### 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}}`. \ No newline at end of file +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}}`. + + +## 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. diff --git a/docs/docs/widgets/text-input.md b/docs/docs/widgets/text-input.md index 17bf8d9003..751d5575f2 100644 --- a/docs/docs/widgets/text-input.md +++ b/docs/docs/widgets/text-input.md @@ -109,3 +109,23 @@ This property only accepts boolean values. If set to `{{true}}`, the widget will :::info Check the **component specific actions** available for this component **[here](/docs/actions/control-component)**. ::: + + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever user a user inputs a value in the component. You can access the value dynamically using JS: `{{components.textinput1.value}}`| + +## Component specific actions (CSA) + +Following actions of text input component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setFocus | Set the focus of the cursor on the text input 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.textinput1.setFocus()` | +| setBlur | Removes the focus of the cursor on the text input 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.textinput1.setBlur()` | +| disable | disable the 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.textinput1.disable(true)` | +| visibility | Set a visibility of the text input 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.textinput1.visibility(false)` | +| setText | Set a text value on the text input 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.textinput1.setText('this is input text')` | +| clear | Clear the entered text from the text input 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.textinput1.clear()` | \ No newline at end of file diff --git a/docs/docs/widgets/text.md b/docs/docs/widgets/text.md index 8b526f6269..5f0bc81be9 100644 --- a/docs/docs/widgets/text.md +++ b/docs/docs/widgets/text.md @@ -74,4 +74,20 @@ 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**. -::: \ No newline at end of file +::: + + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| text | 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}}`| + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| visibility | Set a visibility of the text 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.text1.visibility(false)` | +| setText | Set a text value on the text 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.text1.setText('this is a text')` | \ No newline at end of file diff --git a/docs/docs/widgets/textarea.md b/docs/docs/widgets/textarea.md index 8ff2513929..e670443407 100644 --- a/docs/docs/widgets/textarea.md +++ b/docs/docs/widgets/textarea.md @@ -58,12 +58,20 @@ This is to control the visibility of the widget. If `{{false}}` the widget will This property only accepts boolean values. If set to `{{true}}`, the widget will be locked and becomes non-functional. By default, its value is set to `{{false}}`. -## Actions +## Exposed Variables -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| setText | Set the text. | `text` | -| clear | Clear the text. | | +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value of the text area component. You can access the value dynamically using JS: `{{components.textarea1.value}}`| + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setText | Set the text on the text area 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.textarea1.setText('this is a text')` | +| clear | clear the value from the text area 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.text1.clear()` | :::info Any property having `Fx` button next to its field can be **programmatically configured**. diff --git a/docs/docs/widgets/timeline.md b/docs/docs/widgets/timeline.md index f2d2bdd7e2..ee6cc8b819 100644 --- a/docs/docs/widgets/timeline.md +++ b/docs/docs/widgets/timeline.md @@ -61,3 +61,12 @@ Under the General 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. diff --git a/docs/docs/widgets/timer.md b/docs/docs/widgets/timer.md index 18ba85b865..ec7c14a23d 100644 --- a/docs/docs/widgets/timer.md +++ b/docs/docs/widgets/timer.md @@ -77,4 +77,13 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value of the timer in the following keys: **hour**, **minute**, **second**, and **mSecond**. You can access the value dynamically using JS: `{{components.timer1.value.second}}`| +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/docs/widgets/toggle-switch.md b/docs/docs/widgets/toggle-switch.md index 56e2ed715d..4d11ed9ecf 100644 --- a/docs/docs/widgets/toggle-switch.md +++ b/docs/docs/widgets/toggle-switch.md @@ -73,4 +73,14 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the boolean value i.e `true` or `false` when the toggle is on or off respectively. You can access the value dynamically using JS: `{{components.toggleswitch1.value}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/docs/widgets/tree-select.md b/docs/docs/widgets/tree-select.md index 64b72798a1..5bccb5c366 100644 --- a/docs/docs/widgets/tree-select.md +++ b/docs/docs/widgets/tree-select.md @@ -155,3 +155,16 @@ 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 | +| ----------- | ----------- | +| checked | This variable holds the value of all the checked items on the tree select component. You can access the value dynamically using JS: `{{components.treeselect1.checked[1]}}`| +| expanded | This variable holds the value of expanded items on the tree select component. You can access the value dynamically using JS: `{{components.treeselect1.expanded[0]}}`| +| checkedPathArray | This variable holds the path of the checked items in differet arrays. You can access the value dynamically using JS: `{{components.treeselect1.checkedPathArray[1][1]}}`| +| checkedPathStrings | This variable holds the path of the checked items in strings separated by a dash(-). You can access the value dynamically using JS: `{{components.treeselect1.checkedPathStrings[2]}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/docs/widgets/vertical-divider.md b/docs/docs/widgets/vertical-divider.md index 8cf2894a1c..769920cd73 100644 --- a/docs/docs/widgets/vertical-divider.md +++ b/docs/docs/widgets/vertical-divider.md @@ -54,4 +54,12 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/sidebars.js b/docs/sidebars.js index 7f2ae33f30..73f65d0182 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -303,8 +303,10 @@ const sidebars = { 'how-to/import-external-libraries-using-runpy', 'how-to/import-external-libraries-using-runjs', 'how-to/run-actions-from-runjs', + 'how-to/intentionally-fail-js-query', 'how-to/run-query-at-specified-intervals', 'how-to/access-users-location', + 'how-to/use-s3-signed-url-to-upload-docs', 'how-to/s3-custom-endpoints', 'how-to/oauth2-authorization', 'how-to/upload-files-aws', diff --git a/docs/static/img/datasource-reference/overview/changescope.png b/docs/static/img/datasource-reference/overview/changescope.png new file mode 100644 index 0000000000..95f0df6dbd Binary files /dev/null and b/docs/static/img/datasource-reference/overview/changescope.png differ diff --git a/docs/static/img/datasource-reference/overview/connection.png b/docs/static/img/datasource-reference/overview/connection.png new file mode 100644 index 0000000000..46f77abcdb Binary files /dev/null and b/docs/static/img/datasource-reference/overview/connection.png differ diff --git a/docs/static/img/datasource-reference/overview/create.png b/docs/static/img/datasource-reference/overview/create.png new file mode 100644 index 0000000000..093892dd6b Binary files /dev/null and b/docs/static/img/datasource-reference/overview/create.png differ diff --git a/docs/static/img/datasource-reference/overview/default.png b/docs/static/img/datasource-reference/overview/default.png new file mode 100644 index 0000000000..bb86b2c259 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/default.png differ diff --git a/docs/static/img/datasource-reference/overview/edit.png b/docs/static/img/datasource-reference/overview/edit.png new file mode 100644 index 0000000000..ed5a82134a Binary files /dev/null and b/docs/static/img/datasource-reference/overview/edit.png differ diff --git a/docs/static/img/datasource-reference/overview/error.png b/docs/static/img/datasource-reference/overview/error.png new file mode 100644 index 0000000000..eafe42cef0 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/error.png differ diff --git a/docs/static/img/datasource-reference/overview/global.png b/docs/static/img/datasource-reference/overview/global.png new file mode 100644 index 0000000000..746eacba93 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/global.png differ diff --git a/docs/static/img/datasource-reference/overview/globalquery.png b/docs/static/img/datasource-reference/overview/globalquery.png new file mode 100644 index 0000000000..f2273650fc Binary files /dev/null and b/docs/static/img/datasource-reference/overview/globalquery.png differ diff --git a/docs/static/img/datasource-reference/overview/leftsidebar.png b/docs/static/img/datasource-reference/overview/leftsidebar.png new file mode 100644 index 0000000000..1d6f717eab Binary files /dev/null and b/docs/static/img/datasource-reference/overview/leftsidebar.png differ diff --git a/docs/static/img/datasource-reference/overview/popup.png b/docs/static/img/datasource-reference/overview/popup.png new file mode 100644 index 0000000000..a3d945b374 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/popup.png differ diff --git a/docs/static/img/datasource-reference/overview/queryadd.png b/docs/static/img/datasource-reference/overview/queryadd.png new file mode 100644 index 0000000000..8caf9ff3dd Binary files /dev/null and b/docs/static/img/datasource-reference/overview/queryadd.png differ diff --git a/docs/static/img/datasource-reference/overview/switch.png b/docs/static/img/datasource-reference/overview/switch.png new file mode 100644 index 0000000000..817fe042d7 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/switch.png differ diff --git a/docs/static/img/datasource-reference/overview/view.png b/docs/static/img/datasource-reference/overview/view.png new file mode 100644 index 0000000000..f811c45dd9 Binary files /dev/null and b/docs/static/img/datasource-reference/overview/view.png differ diff --git a/docs/static/img/datasource-reference/smtp/querysmtp.png b/docs/static/img/datasource-reference/smtp/querysmtp.png new file mode 100644 index 0000000000..75a7a8ca82 Binary files /dev/null and b/docs/static/img/datasource-reference/smtp/querysmtp.png differ diff --git a/docs/static/img/how-to/failjs/failjs.gif b/docs/static/img/how-to/failjs/failjs.gif new file mode 100644 index 0000000000..c042f682d4 Binary files /dev/null and b/docs/static/img/how-to/failjs/failjs.gif differ diff --git a/docs/static/img/how-to/uses3presignedurl/copysigned.png b/docs/static/img/how-to/uses3presignedurl/copysigned.png new file mode 100644 index 0000000000..ffdd0793c0 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/copysigned.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/dropdown.png b/docs/static/img/how-to/uses3presignedurl/dropdown.png new file mode 100644 index 0000000000..0c97c1c9a8 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/dropdown.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/eventhandlerrunjs2.png b/docs/static/img/how-to/uses3presignedurl/eventhandlerrunjs2.png new file mode 100644 index 0000000000..21ebd55481 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/eventhandlerrunjs2.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/eventhandlerupload.png b/docs/static/img/how-to/uses3presignedurl/eventhandlerupload.png new file mode 100644 index 0000000000..5670721423 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/eventhandlerupload.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/fetchfiles.png b/docs/static/img/how-to/uses3presignedurl/fetchfiles.png new file mode 100644 index 0000000000..21e548b7af Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/fetchfiles.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/getbuckets.png b/docs/static/img/how-to/uses3presignedurl/getbuckets.png new file mode 100644 index 0000000000..ba62cc882d Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/getbuckets.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/newversion.png b/docs/static/img/how-to/uses3presignedurl/newversion.png new file mode 100644 index 0000000000..aac030e909 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/newversion.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/runjs1.png b/docs/static/img/how-to/uses3presignedurl/runjs1.png new file mode 100644 index 0000000000..0313e21b49 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/runjs1.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/runjs2.png b/docs/static/img/how-to/uses3presignedurl/runjs2.png new file mode 100644 index 0000000000..17a13c6d06 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/runjs2.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/s3connect.png b/docs/static/img/how-to/uses3presignedurl/s3connect.png new file mode 100644 index 0000000000..7ade00fada Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/s3connect.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/template.png b/docs/static/img/how-to/uses3presignedurl/template.png new file mode 100644 index 0000000000..761705dee4 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/template.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/upload.png b/docs/static/img/how-to/uses3presignedurl/upload.png new file mode 100644 index 0000000000..93f1924c14 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/upload.png differ diff --git a/docs/static/img/how-to/uses3presignedurl/uploadbutton.png b/docs/static/img/how-to/uses3presignedurl/uploadbutton.png new file mode 100644 index 0000000000..f580239948 Binary files /dev/null and b/docs/static/img/how-to/uses3presignedurl/uploadbutton.png differ diff --git a/docs/static/img/tutorial/manage-users-groups/dspermission.png b/docs/static/img/tutorial/manage-users-groups/dspermission.png new file mode 100644 index 0000000000..69e1c57a2d Binary files /dev/null and b/docs/static/img/tutorial/manage-users-groups/dspermission.png differ diff --git a/docs/static/img/tutorial/manage-users-groups/gdspermission.png b/docs/static/img/tutorial/manage-users-groups/gdspermission.png new file mode 100644 index 0000000000..35b8423a69 Binary files /dev/null and b/docs/static/img/tutorial/manage-users-groups/gdspermission.png differ diff --git a/docs/static/img/widgets/container/container.png b/docs/static/img/widgets/container/container.png new file mode 100644 index 0000000000..d98e9f9a5d Binary files /dev/null and b/docs/static/img/widgets/container/container.png differ diff --git a/docs/static/img/widgets/container/scrollcontainer.png b/docs/static/img/widgets/container/scrollcontainer.png new file mode 100644 index 0000000000..9d77d8eea8 Binary files /dev/null and b/docs/static/img/widgets/container/scrollcontainer.png differ diff --git a/docs/static/img/widgets/table/ui.png b/docs/static/img/widgets/table/ui.png new file mode 100644 index 0000000000..7a42690459 Binary files /dev/null and b/docs/static/img/widgets/table/ui.png differ diff --git a/docs/versioned_docs/version-1.x.x/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-1.x.x/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-1.x.x/how-to/intentionally-fail-js-query.md @@ -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. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-1.x.x/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-1.x.x/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-1.x.x/how-to/use-s3-presigned-url-to-upload-docs.md @@ -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. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- 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. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- 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 + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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 + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - 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": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. + diff --git a/docs/versioned_docs/version-2.0.0/getting-started.md b/docs/versioned_docs/version-2.0.0/getting-started.md index 3d944f62c9..4e83aaca55 100644 --- a/docs/versioned_docs/version-2.0.0/getting-started.md +++ b/docs/versioned_docs/version-2.0.0/getting-started.md @@ -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**.
@@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::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 :::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 :::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 diff --git a/docs/versioned_docs/version-2.0.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.0.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.0.0/how-to/intentionally-fail-js-query.md @@ -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. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.0.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.0.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.0.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -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. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- 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. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- 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 + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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 + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - 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": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. + diff --git a/docs/versioned_docs/version-2.1.0/getting-started.md b/docs/versioned_docs/version-2.1.0/getting-started.md index 3d944f62c9..4e83aaca55 100644 --- a/docs/versioned_docs/version-2.1.0/getting-started.md +++ b/docs/versioned_docs/version-2.1.0/getting-started.md @@ -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**.
@@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::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 :::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 :::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 diff --git a/docs/versioned_docs/version-2.1.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.1.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.1.0/how-to/intentionally-fail-js-query.md @@ -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. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.1.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.1.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.1.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -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. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- 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. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- 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 + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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 + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - 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": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. + diff --git a/docs/versioned_docs/version-2.2.0/getting-started.md b/docs/versioned_docs/version-2.2.0/getting-started.md index 3d944f62c9..4e83aaca55 100644 --- a/docs/versioned_docs/version-2.2.0/getting-started.md +++ b/docs/versioned_docs/version-2.2.0/getting-started.md @@ -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**.
@@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::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 :::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 :::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 diff --git a/docs/versioned_docs/version-2.2.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.2.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.2.0/how-to/intentionally-fail-js-query.md @@ -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. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.2.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.2.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.2.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -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. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- 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. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- 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 + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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 + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - 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": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. + diff --git a/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md b/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md index 10ebe5551b..43332fd236 100644 --- a/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md +++ b/docs/versioned_docs/version-2.3.0/Enterprise/superadmin.md @@ -17,13 +17,14 @@ 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/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) | ❌ | ✅ | | [Manage any workspace's setting (Groups/SSO/Workspace Variables)](#manage-workspace-setting-groupsssoworkspace-variables) | ❌ | ✅ | | [Manage all users from all the workspaces in the instance](#checking-all-the-users-in-the-instance) | ❌ | ✅ | | [Make any user Super Admin](#make-the-user-super-admin) | ❌ | ✅ | -| [Restrict personal workspace of invited users](#allow-users-to-create-personal-workspace) | ❌ | ✅ | +| [Restrict creation of personal workspace of users](#restrict-creation-of-personal-workspace-of-users) | ❌ | ✅ |
@@ -117,11 +118,11 @@ The user will become Super Admin and the Type column will update from **`workspa
-### Allow users to create personal workspace +### Restrict creation of personal workspace of users When a user joins a workspace, they are provided with their own personal workspace and option to create new workspaces. -Super Admins can control this behavior from the Manage Instance Settings page, they can **toggle off** the option to **Allow personal workspace**. Now whenever a user joins a workspace they won't be provided a personal workspace nor they will be able to create a new workspace in the instance. +Super Admins can **control** this behavior from the Manage Instance Settings page, they can **toggle off** the option to **Allow personal workspace**. Now whenever a user joins a workspace they won't be provided a personal workspace nor they will be able to create a new workspace in the instance.
diff --git a/docs/versioned_docs/version-2.3.0/data-sources/overview.md b/docs/versioned_docs/version-2.3.0/data-sources/overview.md index ec7527f8af..7e72107e61 100644 --- a/docs/versioned_docs/version-2.3.0/data-sources/overview.md +++ b/docs/versioned_docs/version-2.3.0/data-sources/overview.md @@ -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**. +:::
@@ -13,30 +17,115 @@ Datasources pull in and push data to any source including databases, external AP
-## 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. +
-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 + Datasources: Overview -
+
- Datasources: Overview +2. Click on the **Add new datasource** button, a modal will pop-up with all the available global datasources. +
-
+ Datasources: Overview - 2. **Query Panel**: Go to the query panel at the bottom, click on the `+Add` button and then click `Add datasource` button +
-
+3. Select the datasource, enter the **Credentials** and **Save** the datasource. +
- Datasources: Overview + Datasources: Overview -
+
-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**. +
+ + Datasources: Overview + +
+ +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. +
+ + Datasources: Overview + +
+ +## 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. +
+ + Datasources: Overview + +
+ +2. Click on the kebab menu next to the connected datasource, select the **change scope** option. +
+ + Datasources: Overview + +
+ +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**. +
+ + Datasources: Overview + +
+ + +## 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/)** + +
+ + Datasources: Overview + +
+ +## 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 only **Create** permission is set then the users of the group will only be able to add new datasources on the workspace, 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. +
+ + Datasources: Overview + +
+ + - 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. +
+ + Datasources: Overview + +
+ +- **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. +
+ + Datasources: Overview + +
+ + - 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. +
+ + Datasources: Overview + +
-:::info -ToolJet allows you to transform the data returned by datasources using **[Transformations](/docs/tutorial/transformations)** -::: diff --git a/docs/versioned_docs/version-2.3.0/getting-started.md b/docs/versioned_docs/version-2.3.0/getting-started.md index 3d944f62c9..71f3aa3166 100644 --- a/docs/versioned_docs/version-2.3.0/getting-started.md +++ b/docs/versioned_docs/version-2.3.0/getting-started.md @@ -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**.
diff --git a/docs/versioned_docs/version-2.3.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.3.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.3.0/how-to/intentionally-fail-js-query.md @@ -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. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.3.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.3.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -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. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- 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. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- 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 + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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 + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - 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": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. + diff --git a/docs/versioned_docs/version-2.3.0/org-management/permissions.md b/docs/versioned_docs/version-2.3.0/org-management/permissions.md index 5e33f5cb2c..b13433e0ef 100644 --- a/docs/versioned_docs/version-2.3.0/org-management/permissions.md +++ b/docs/versioned_docs/version-2.3.0/org-management/permissions.md @@ -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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md b/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md index 95991fd7fa..5877d2f075 100644 --- a/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md +++ b/docs/versioned_docs/version-2.3.0/tutorial/manage-users-groups.md @@ -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**.
@@ -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.
@@ -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.
@@ -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/data-sources/overview)
-permissions +permissions + +
+ +#### Datasources: + +Only Admins and Super Admins can define what datasources can be **viewed** or **edited** by the users of that group. + +
+ +permissions
:::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 diff --git a/docs/versioned_docs/version-2.3.0/widgets/bounded-box.md b/docs/versioned_docs/version-2.3.0/widgets/bounded-box.md index 4a7edb33be..6148c1cc6d 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/bounded-box.md +++ b/docs/versioned_docs/version-2.3.0/widgets/bounded-box.md @@ -97,4 +97,24 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/button-group.md b/docs/versioned_docs/version-2.3.0/widgets/button-group.md index f415e830c7..09c5821663 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/button-group.md +++ b/docs/versioned_docs/version-2.3.0/widgets/button-group.md @@ -92,4 +92,15 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/button.md b/docs/versioned_docs/version-2.3.0/widgets/button.md index dd86c3b615..0d64c4bd6e 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/button.md +++ b/docs/versioned_docs/version-2.3.0/widgets/button.md @@ -93,4 +93,20 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/calendar.md b/docs/versioned_docs/version-2.3.0/widgets/calendar.md index 454a2393da..115935cd52 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/calendar.md +++ b/docs/versioned_docs/version-2.3.0/widgets/calendar.md @@ -197,3 +197,15 @@ 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/chart.md b/docs/versioned_docs/version-2.3.0/widgets/chart.md index 9760de273f..5db81b7e06 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/chart.md +++ b/docs/versioned_docs/version-2.3.0/widgets/chart.md @@ -387,3 +387,11 @@ 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/checkbox.md b/docs/versioned_docs/version-2.3.0/widgets/checkbox.md index dbdc0f0650..9e6639d508 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/checkbox.md +++ b/docs/versioned_docs/version-2.3.0/widgets/checkbox.md @@ -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)` | \ No newline at end of file +| 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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/circular-progressbar.md b/docs/versioned_docs/version-2.3.0/widgets/circular-progressbar.md index 47064de88e..454c0464a0 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/circular-progressbar.md +++ b/docs/versioned_docs/version-2.3.0/widgets/circular-progressbar.md @@ -75,4 +75,13 @@ Under the General 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. -::: \ No newline at end of file +::: + + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/code-editor.md b/docs/versioned_docs/version-2.3.0/widgets/code-editor.md index 612593221e..6434cd0e3f 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/code-editor.md +++ b/docs/versioned_docs/version-2.3.0/widgets/code-editor.md @@ -206,4 +206,14 @@ Under the General 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`. | \ No newline at end of file +| 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/color-picker.md b/docs/versioned_docs/version-2.3.0/widgets/color-picker.md index 36a8fc6658..2c3260315c 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/color-picker.md +++ b/docs/versioned_docs/version-2.3.0/widgets/color-picker.md @@ -81,4 +81,20 @@ Any property having `Fx` button next to its field can be **programmatically conf ToolJet - Widget Reference - Color Picker -
\ No newline at end of file +
+ +## 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')` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/container.md b/docs/versioned_docs/version-2.3.0/widgets/container.md index fc58141f51..8fe5d3506a 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/container.md +++ b/docs/versioned_docs/version-2.3.0/widgets/container.md @@ -57,4 +57,12 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/custom-component.md b/docs/versioned_docs/version-2.3.0/widgets/custom-component.md index 385252605d..953e8dcd83 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/custom-component.md +++ b/docs/versioned_docs/version-2.3.0/widgets/custom-component.md @@ -103,3 +103,14 @@ Under the General 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/date-range-picker.md b/docs/versioned_docs/version-2.3.0/widgets/date-range-picker.md index 642c97b427..72ce95d38a 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/date-range-picker.md +++ b/docs/versioned_docs/version-2.3.0/widgets/date-range-picker.md @@ -78,4 +78,17 @@ 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**. -::: \ No newline at end of file +::: + + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/datepicker.md b/docs/versioned_docs/version-2.3.0/widgets/datepicker.md index 19001e9fb6..22f19e4955 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/datepicker.md +++ b/docs/versioned_docs/version-2.3.0/widgets/datepicker.md @@ -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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/divider.md b/docs/versioned_docs/version-2.3.0/widgets/divider.md index b027ae96d2..399c75a1e7 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/divider.md +++ b/docs/versioned_docs/version-2.3.0/widgets/divider.md @@ -53,4 +53,13 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/dropdown.md b/docs/versioned_docs/version-2.3.0/widgets/dropdown.md index e4cdca4a72..9bd7ee5a9b 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/dropdown.md +++ b/docs/versioned_docs/version-2.3.0/widgets/dropdown.md @@ -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 | \ No newline at end of file +| 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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/file-picker.md b/docs/versioned_docs/version-2.3.0/widgets/file-picker.md index 6d7efd4219..380d2c9a65 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/file-picker.md +++ b/docs/versioned_docs/version-2.3.0/widgets/file-picker.md @@ -165,8 +165,15 @@ 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 -| Action | Description | Properties | -| ----------- | ----------- | ------------ | -| `clearFiles()` | It will clear the selected files | None | +## Exposed Variables + +| 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()` | diff --git a/docs/versioned_docs/version-2.3.0/widgets/form.md b/docs/versioned_docs/version-2.3.0/widgets/form.md index 8d368d65fd..ba5f9e5e70 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/form.md +++ b/docs/versioned_docs/version-2.3.0/widgets/form.md @@ -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**. -::: \ No newline at end of file +::: + + +## 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()` | + diff --git a/docs/versioned_docs/version-2.3.0/widgets/html.md b/docs/versioned_docs/version-2.3.0/widgets/html.md index cdb523f9d7..a4f65f8734 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/html.md +++ b/docs/versioned_docs/version-2.3.0/widgets/html.md @@ -70,3 +70,12 @@ Under the General 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/icon.md b/docs/versioned_docs/version-2.3.0/widgets/icon.md index 9cd9fe3bd9..168824e22d 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/icon.md +++ b/docs/versioned_docs/version-2.3.0/widgets/icon.md @@ -62,3 +62,17 @@ 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()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/iframe.md b/docs/versioned_docs/version-2.3.0/widgets/iframe.md index 3157b9f979..3fdaf72c04 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/iframe.md +++ b/docs/versioned_docs/version-2.3.0/widgets/iframe.md @@ -64,4 +64,13 @@ 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**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/image.md b/docs/versioned_docs/version-2.3.0/widgets/image.md index f84ee6f9c3..6ca6250b6c 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/image.md +++ b/docs/versioned_docs/version-2.3.0/widgets/image.md @@ -104,4 +104,13 @@ 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**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/kanban-board.md b/docs/versioned_docs/version-2.3.0/widgets/kanban-board.md index 0dbb693192..5f55a31b80 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/kanban-board.md +++ b/docs/versioned_docs/version-2.3.0/widgets/kanban-board.md @@ -118,6 +118,7 @@ Under the General accordion, you can set the value in the string format. | Visibility | This is to control the visibility of the widget. If `{{false}}`/disabled the widget will not visible after the app is deployed. By default, it's enabled (set to `{{true}}`). | | Accent color | You can change the accent color of the column title by entering the Hex color code or choosing a color of your choice from the color picker. | + ## Exposed variables
@@ -134,4 +135,15 @@ Under the General 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}}` | \ No newline at end of file +| 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')` | diff --git a/docs/versioned_docs/version-2.3.0/widgets/link.md b/docs/versioned_docs/version-2.3.0/widgets/link.md index ca20002102..de7628dbab 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/link.md +++ b/docs/versioned_docs/version-2.3.0/widgets/link.md @@ -65,4 +65,16 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/listview.md b/docs/versioned_docs/version-2.3.0/widgets/listview.md index 41a8d42385..288401f339 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/listview.md +++ b/docs/versioned_docs/version-2.3.0/widgets/listview.md @@ -137,4 +137,15 @@ 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. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/map.md b/docs/versioned_docs/version-2.3.0/widgets/map.md index 2469048e68..97b8764cc8 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/map.md +++ b/docs/versioned_docs/version-2.3.0/widgets/map.md @@ -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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/modal.md b/docs/versioned_docs/version-2.3.0/widgets/modal.md index eb196123ac..3c0227b082 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/modal.md +++ b/docs/versioned_docs/version-2.3.0/widgets/modal.md @@ -98,4 +98,19 @@ 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. -::: \ No newline at end of file +::: + + +## 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()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/multiselect.md b/docs/versioned_docs/version-2.3.0/widgets/multiselect.md index cdc83f7ab9..d07d9358b4 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/multiselect.md +++ b/docs/versioned_docs/version-2.3.0/widgets/multiselect.md @@ -75,15 +75,20 @@ Toggle on or off to control the visibility of the widget. You can programmatical 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}}`. -## Actions +## Exposed Variables -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| `selectOption` | Select options. | pass options as parameter. ex: `components.multiselect1.selectOption(1)` | -| `deselectOption` | Deselect options.| pass options as parameter. ex: `components.multiselect1.deselectOption(1)` | -| `clearSelections` | Clear all selection. | ex: `components.multiselect1.clearSelections()` | +| 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) -:::info -Any property having `Fx` button next to its field can be **programmatically configured**. -::: +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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/number-input.md b/docs/versioned_docs/version-2.3.0/widgets/number-input.md index f72242234c..115a1a37d1 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/number-input.md +++ b/docs/versioned_docs/version-2.3.0/widgets/number-input.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/pagination.md b/docs/versioned_docs/version-2.3.0/widgets/pagination.md index 2ee0003cc5..dae8985980 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/pagination.md +++ b/docs/versioned_docs/version-2.3.0/widgets/pagination.md @@ -67,3 +67,14 @@ 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/password-input.md b/docs/versioned_docs/version-2.3.0/widgets/password-input.md index 019ec8fae3..b692c861a1 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/password-input.md +++ b/docs/versioned_docs/version-2.3.0/widgets/password-input.md @@ -79,4 +79,15 @@ 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**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/pdf.md b/docs/versioned_docs/version-2.3.0/widgets/pdf.md index bc665697fe..6af77f190f 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/pdf.md +++ b/docs/versioned_docs/version-2.3.0/widgets/pdf.md @@ -54,3 +54,12 @@ Under the General 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/qr-scanner.md b/docs/versioned_docs/version-2.3.0/widgets/qr-scanner.md index edab5ade23..db4bd789ef 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/qr-scanner.md +++ b/docs/versioned_docs/version-2.3.0/widgets/qr-scanner.md @@ -69,3 +69,8 @@ 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/radio-button.md b/docs/versioned_docs/version-2.3.0/widgets/radio-button.md index 9de2d08c16..26c3c5c725 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/radio-button.md +++ b/docs/versioned_docs/version-2.3.0/widgets/radio-button.md @@ -94,8 +94,14 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
-## Actions +## Exposed variables -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| selectOption | Select an option from the radio buttons. | `option` eg: `component.radiobutton1.selectOption('one')` | \ No newline at end of file +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')` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/range-slider.md b/docs/versioned_docs/version-2.3.0/widgets/range-slider.md index 5c58220496..6a833e7571 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/range-slider.md +++ b/docs/versioned_docs/version-2.3.0/widgets/range-slider.md @@ -74,3 +74,14 @@ 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/rich-text-editor.md b/docs/versioned_docs/version-2.3.0/widgets/rich-text-editor.md index 2267259807..96449ca498 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/rich-text-editor.md +++ b/docs/versioned_docs/version-2.3.0/widgets/rich-text-editor.md @@ -54,3 +54,14 @@ 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 + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever a user enters a value in the rich text editor component. You can access the value dynamically using JS: `{{components.richtexteditor1.value}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.3.0/widgets/spinner.md b/docs/versioned_docs/version-2.3.0/widgets/spinner.md index 8557673342..06fed77b84 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/spinner.md +++ b/docs/versioned_docs/version-2.3.0/widgets/spinner.md @@ -46,4 +46,13 @@ Change the color of the Spinner by entering the `Hex color code` or choosing a c ### Size -Change the size of the Spinner by selecting options from the dropdown. It has small and large sizes available. \ No newline at end of file +Change the size of the Spinner by selecting options from the dropdown. It has small and large sizes available. + + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/star-rating.md b/docs/versioned_docs/version-2.3.0/widgets/star-rating.md index d204438589..39040129a0 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/star-rating.md +++ b/docs/versioned_docs/version-2.3.0/widgets/star-rating.md @@ -83,3 +83,14 @@ 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 + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever a rating is added on the component. The variable holds a numerical value. You can access the value dynamically using JS: `{{components.colorpicker1.selectedColorHex}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.3.0/widgets/statistics.md b/docs/versioned_docs/version-2.3.0/widgets/statistics.md index b2fa668f13..28bbf365cb 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/statistics.md +++ b/docs/versioned_docs/version-2.3.0/widgets/statistics.md @@ -91,3 +91,12 @@ Toggle on or off to control the visibility of the widget. You can programmatical :::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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/steps.md b/docs/versioned_docs/version-2.3.0/widgets/steps.md index 5c486e0ecb..f43628f9c3 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/steps.md +++ b/docs/versioned_docs/version-2.3.0/widgets/steps.md @@ -89,4 +89,14 @@ Toggle on or off to control the visibility of the widget. You can programmatical :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| currentStepId | This variable holds the id of the currently selected step on the step component. You can access the value dynamically using JS: `{{components.steps1.currentStepId}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.3.0/widgets/svg-image.md b/docs/versioned_docs/version-2.3.0/widgets/svg-image.md index 76500c1eb8..b3a7a5ac7f 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/svg-image.md +++ b/docs/versioned_docs/version-2.3.0/widgets/svg-image.md @@ -56,4 +56,12 @@ Toggle on or off to display the widget in mobile view. You can programmatically ### 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}}`. \ No newline at end of file +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}}`. + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/table.md b/docs/versioned_docs/version-2.3.0/widgets/table.md index 15b05f6fbd..93f9c5dff2 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/table.md +++ b/docs/versioned_docs/version-2.3.0/widgets/table.md @@ -327,3 +327,16 @@ This event is triggered when filter is added, removed, or updated from the filte :::info Any property having `Fx` button next to its field can be **programmatically configured**. ::: + + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setPage | Set the page on the table via 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.table1.setPage(2)` | +| selectRow | Select the row on the table using via 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.table1.selectRow('id','11')` | +| deselectRow | Deselect the row on the table via 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.table1.deselectRow()` | +| discardChanges | Discard the changes from the table when a cell is edited via 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.table1.discardChanges()` | +| discardNewlyAddedRows | Discard the newly added rows from the add new row popup on the table via 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.table1.discardNewlyAddedRows()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/tabs.md b/docs/versioned_docs/version-2.3.0/widgets/tabs.md index 0bfa2a8a2a..60930903ce 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/tabs.md +++ b/docs/versioned_docs/version-2.3.0/widgets/tabs.md @@ -113,4 +113,18 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| currentTab | This variable holds the id of the current tab selected on the tabs component. You can access the value dynamically using JS: `{{components.tabs1.currentTab}}`| + +## Component specific actions (CSA) + +Following actions of Tabs component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setTab | Set the current tab of the tabs 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.tabs1.setTab(1)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/tags.md b/docs/versioned_docs/version-2.3.0/widgets/tags.md index ea01906c69..468a37f05d 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/tags.md +++ b/docs/versioned_docs/version-2.3.0/widgets/tags.md @@ -57,4 +57,12 @@ Toggle on or off to display the widget in mobile view. You can programmatically ### 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}}`. \ No newline at end of file +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}}`. + +## 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. diff --git a/docs/versioned_docs/version-2.3.0/widgets/text-input.md b/docs/versioned_docs/version-2.3.0/widgets/text-input.md index 17bf8d9003..6e02dcd90d 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/text-input.md +++ b/docs/versioned_docs/version-2.3.0/widgets/text-input.md @@ -109,3 +109,22 @@ This property only accepts boolean values. If set to `{{true}}`, the widget will :::info Check the **component specific actions** available for this component **[here](/docs/actions/control-component)**. ::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever user a user inputs a value in the component. You can access the value dynamically using JS: `{{components.textinput1.value}}`| + +## Component specific actions (CSA) + +Following actions of text input component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setFocus | Set the focus of the cursor on the text input 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.textinput1.setFocus()` | +| setBlur | Removes the focus of the cursor on the text input 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.textinput1.setBlur()` | +| disable | disable the 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.textinput1.disable(true)` | +| visibility | Set a visibility of the text input 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.textinput1.visibility(false)` | +| setText | Set a text value on the text input 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.textinput1.setText('this is input text')` | +| clear | Clear the entered text from the text input 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.textinput1.clear()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/text.md b/docs/versioned_docs/version-2.3.0/widgets/text.md index 8b526f6269..6f64632db3 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/text.md +++ b/docs/versioned_docs/version-2.3.0/widgets/text.md @@ -74,4 +74,19 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| text | 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}}`| + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| visibility | Set a visibility of the text 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.text1.visibility(false)` | +| setText | Set a text value on the text 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.text1.setText('this is a text')` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.3.0/widgets/textarea.md b/docs/versioned_docs/version-2.3.0/widgets/textarea.md index 8ff2513929..8b46c23805 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/textarea.md +++ b/docs/versioned_docs/version-2.3.0/widgets/textarea.md @@ -58,12 +58,21 @@ This is to control the visibility of the widget. If `{{false}}` the widget will This property only accepts boolean values. If set to `{{true}}`, the widget will be locked and becomes non-functional. By default, its value is set to `{{false}}`. -## Actions -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| setText | Set the text. | `text` | -| clear | Clear the text. | | +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value of the text area component. You can access the value dynamically using JS: `{{components.textarea1.value}}`| + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setText | Set the text on the text area 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.textarea1.setText('this is a text')` | +| clear | clear the value from the text area 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.text1.clear()` | :::info Any property having `Fx` button next to its field can be **programmatically configured**. diff --git a/docs/versioned_docs/version-2.3.0/widgets/timeline.md b/docs/versioned_docs/version-2.3.0/widgets/timeline.md index f2d2bdd7e2..35a2945cbd 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/timeline.md +++ b/docs/versioned_docs/version-2.3.0/widgets/timeline.md @@ -61,3 +61,12 @@ Under the General 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. + diff --git a/docs/versioned_docs/version-2.3.0/widgets/timer.md b/docs/versioned_docs/version-2.3.0/widgets/timer.md index 18ba85b865..ec7c14a23d 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/timer.md +++ b/docs/versioned_docs/version-2.3.0/widgets/timer.md @@ -77,4 +77,13 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value of the timer in the following keys: **hour**, **minute**, **second**, and **mSecond**. You can access the value dynamically using JS: `{{components.timer1.value.second}}`| +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.3.0/widgets/toggle-switch.md b/docs/versioned_docs/version-2.3.0/widgets/toggle-switch.md index 56e2ed715d..4d11ed9ecf 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/toggle-switch.md +++ b/docs/versioned_docs/version-2.3.0/widgets/toggle-switch.md @@ -73,4 +73,14 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the boolean value i.e `true` or `false` when the toggle is on or off respectively. You can access the value dynamically using JS: `{{components.toggleswitch1.value}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.3.0/widgets/tree-select.md b/docs/versioned_docs/version-2.3.0/widgets/tree-select.md index 64b72798a1..5bccb5c366 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/tree-select.md +++ b/docs/versioned_docs/version-2.3.0/widgets/tree-select.md @@ -155,3 +155,16 @@ 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 | +| ----------- | ----------- | +| checked | This variable holds the value of all the checked items on the tree select component. You can access the value dynamically using JS: `{{components.treeselect1.checked[1]}}`| +| expanded | This variable holds the value of expanded items on the tree select component. You can access the value dynamically using JS: `{{components.treeselect1.expanded[0]}}`| +| checkedPathArray | This variable holds the path of the checked items in differet arrays. You can access the value dynamically using JS: `{{components.treeselect1.checkedPathArray[1][1]}}`| +| checkedPathStrings | This variable holds the path of the checked items in strings separated by a dash(-). You can access the value dynamically using JS: `{{components.treeselect1.checkedPathStrings[2]}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.3.0/widgets/vertical-divider.md b/docs/versioned_docs/version-2.3.0/widgets/vertical-divider.md index 8cf2894a1c..769920cd73 100644 --- a/docs/versioned_docs/version-2.3.0/widgets/vertical-divider.md +++ b/docs/versioned_docs/version-2.3.0/widgets/vertical-divider.md @@ -54,4 +54,12 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md b/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md index 63262b98e9..43332fd236 100644 --- a/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md +++ b/docs/versioned_docs/version-2.4.0/Enterprise/superadmin.md @@ -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/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) | ❌ | ✅ | diff --git a/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md b/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md index 465ba553c3..efb8d897fe 100644 --- a/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md +++ b/docs/versioned_docs/version-2.4.0/contributing-guide/troubleshooting/eslint.md @@ -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: {...}**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/data-sources/overview.md b/docs/versioned_docs/version-2.4.0/data-sources/overview.md index ec7527f8af..64eefc507c 100644 --- a/docs/versioned_docs/version-2.4.0/data-sources/overview.md +++ b/docs/versioned_docs/version-2.4.0/data-sources/overview.md @@ -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**. +:::
@@ -13,30 +17,115 @@ Datasources pull in and push data to any source including databases, external AP
-## 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. +
-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 + Datasources: Overview -
+
- Datasources: Overview +2. Click on the **Add new datasource** button, a modal will pop-up with all the available global datasources. +
-
+ Datasources: Overview - 2. **Query Panel**: Go to the query panel at the bottom, click on the `+Add` button and then click `Add datasource` button +
-
+3. Select the datasource, enter the **Credentials** and **Save** the datasource. +
- Datasources: Overview + Datasources: Overview -
+
-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**. +
+ + Datasources: Overview + +
+ +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. +
+ + Datasources: Overview + +
+ +## 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. +
+ + Datasources: Overview + +
+ +2. Click on the kebab menu next to the connected datasource, select the **change scope** option. +
+ + Datasources: Overview + +
+ +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**. +
+ + Datasources: Overview + +
+ + +## 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/)** + +
+ + Datasources: Overview + +
+ +## 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. +
+ + Datasources: Overview + +
+ + - 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. +
+ + Datasources: Overview + +
+ +- **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. +
+ + Datasources: Overview + +
+ + - 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. +
+ + Datasources: Overview + +
-:::info -ToolJet allows you to transform the data returned by datasources using **[Transformations](/docs/tutorial/transformations)** -::: diff --git a/docs/versioned_docs/version-2.4.0/getting-started.md b/docs/versioned_docs/version-2.4.0/getting-started.md index 3d944f62c9..4e83aaca55 100644 --- a/docs/versioned_docs/version-2.4.0/getting-started.md +++ b/docs/versioned_docs/version-2.4.0/getting-started.md @@ -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**.
@@ -143,7 +143,7 @@ Learn more about the **[ToolJet Database here](/docs/tooljet-database)**
:::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
:::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
:::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 diff --git a/docs/versioned_docs/version-2.4.0/how-to/intentionally-fail-js-query.md b/docs/versioned_docs/version-2.4.0/how-to/intentionally-fail-js-query.md new file mode 100644 index 0000000000..bc7750ec4c --- /dev/null +++ b/docs/versioned_docs/version-2.4.0/how-to/intentionally-fail-js-query.md @@ -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. + +
+ + Intentionally fail a RunJS query + +
+ +:::info +Most common use-case for intentionally failing a query is **debugging**. +::: \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/how-to/use-s3-presigned-url-to-upload-docs.md b/docs/versioned_docs/version-2.4.0/how-to/use-s3-presigned-url-to-upload-docs.md new file mode 100644 index 0000000000..61dd6448d3 --- /dev/null +++ b/docs/versioned_docs/version-2.4.0/how-to/use-s3-presigned-url-to-upload-docs.md @@ -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. +::: + +
+ +Use S3 pre-signed URL to upload documents: Choose template + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: new version + +
+ +- 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. + ::: + +
+ + Use S3 pre-signed URL to upload documents: add datasource + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: getBuckets query + +
+ +- Running the **getBuckets** query will load all the buckets in the dropdown in the app. + +
+ + Use S3 pre-signed URL to upload documents: loading buckets + +
+ +- 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. + +
+ + Use S3 pre-signed URL to upload documents: list objects in a bucket + +
+ +- 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 + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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 + ``` + +
+ + Use S3 pre-signed URL to upload documents + +
+ + - 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": [] + } + ] + ``` + ::: + +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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**. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. +
+ + Use S3 pre-signed URL to upload documents + +
+ +- 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. + diff --git a/docs/versioned_docs/version-2.4.0/org-management/permissions.md b/docs/versioned_docs/version-2.4.0/org-management/permissions.md index 5e33f5cb2c..b13433e0ef 100644 --- a/docs/versioned_docs/version-2.4.0/org-management/permissions.md +++ b/docs/versioned_docs/version-2.4.0/org-management/permissions.md @@ -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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/setup/env-vars.md b/docs/versioned_docs/version-2.4.0/setup/env-vars.md index ee546a8cbb..8f98ef8937 100644 --- a/docs/versioned_docs/version-2.4.0/setup/env-vars.md +++ b/docs/versioned_docs/version-2.4.0/setup/env-vars.md @@ -57,7 +57,7 @@ ToolJet by default tries to create database based on `PG_DB` variable set and ad #### Check for updates ( optional ) -Self-hosted version of ToolJet pings our server to fetch the latest product updates every 24 hours. You can disable this by setting the value of `CHECK_FOR_UPDATES` environment variable to `0`. This feature is enabled by default. +Self-hosted version of ToolJet pings our server to fetch the latest product updates every 24 hours. You can disable this by setting the value of `CHECK_FOR_UPDATES` environment variable to `false`. This feature is enabled by default. #### Comment feature enable ( optional ) @@ -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 | diff --git a/docs/versioned_docs/version-2.4.0/tracking.md b/docs/versioned_docs/version-2.4.0/tracking.md index 6033e14f19..bde3fce838 100644 --- a/docs/versioned_docs/version-2.4.0/tracking.md +++ b/docs/versioned_docs/version-2.4.0/tracking.md @@ -13,7 +13,7 @@ ToolJet does not store any data fetched from the data sources. ToolJet acts as a ## Server :::tip -Self-hosted version of ToolJet pings our server to fetch the latest product updates every 24 hours. You can disable this by setting the value of `CHECK_FOR_UPDATES` environment variable to `0`. This feature is enabled by default. +Self-hosted version of ToolJet pings our server to fetch the latest product updates every 24 hours. You can disable this by setting the value of `CHECK_FOR_UPDATES` environment variable to `false`. This feature is enabled by default. ::: ## Client diff --git a/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md b/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md index 95991fd7fa..5877d2f075 100644 --- a/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md +++ b/docs/versioned_docs/version-2.4.0/tutorial/manage-users-groups.md @@ -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**.
@@ -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.
@@ -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.
@@ -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/data-sources/overview)
-permissions +permissions + +
+ +#### Datasources: + +Only Admins and Super Admins can define what datasources can be **viewed** or **edited** by the users of that group. + +
+ +permissions
:::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 diff --git a/docs/versioned_docs/version-2.4.0/widgets/bounded-box.md b/docs/versioned_docs/version-2.4.0/widgets/bounded-box.md index 4a7edb33be..855f65cb40 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/bounded-box.md +++ b/docs/versioned_docs/version-2.4.0/widgets/bounded-box.md @@ -97,4 +97,23 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/button-group.md b/docs/versioned_docs/version-2.4.0/widgets/button-group.md index f415e830c7..09c5821663 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/button-group.md +++ b/docs/versioned_docs/version-2.4.0/widgets/button-group.md @@ -92,4 +92,15 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/button.md b/docs/versioned_docs/version-2.4.0/widgets/button.md index dd86c3b615..0d64c4bd6e 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/button.md +++ b/docs/versioned_docs/version-2.4.0/widgets/button.md @@ -93,4 +93,20 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/calendar.md b/docs/versioned_docs/version-2.4.0/widgets/calendar.md index 454a2393da..115935cd52 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/calendar.md +++ b/docs/versioned_docs/version-2.4.0/widgets/calendar.md @@ -197,3 +197,15 @@ 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/chart.md b/docs/versioned_docs/version-2.4.0/widgets/chart.md index 9760de273f..5db81b7e06 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/chart.md +++ b/docs/versioned_docs/version-2.4.0/widgets/chart.md @@ -387,3 +387,11 @@ 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/checkbox.md b/docs/versioned_docs/version-2.4.0/widgets/checkbox.md index dbdc0f0650..9e6639d508 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/checkbox.md +++ b/docs/versioned_docs/version-2.4.0/widgets/checkbox.md @@ -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)` | \ No newline at end of file +| 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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/circular-progressbar.md b/docs/versioned_docs/version-2.4.0/widgets/circular-progressbar.md index 47064de88e..454c0464a0 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/circular-progressbar.md +++ b/docs/versioned_docs/version-2.4.0/widgets/circular-progressbar.md @@ -75,4 +75,13 @@ Under the General 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. -::: \ No newline at end of file +::: + + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/code-editor.md b/docs/versioned_docs/version-2.4.0/widgets/code-editor.md index 612593221e..ce9dce63d0 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/code-editor.md +++ b/docs/versioned_docs/version-2.4.0/widgets/code-editor.md @@ -206,4 +206,15 @@ Under the General 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`. | \ No newline at end of file +| 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/color-picker.md b/docs/versioned_docs/version-2.4.0/widgets/color-picker.md index 36a8fc6658..2c3260315c 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/color-picker.md +++ b/docs/versioned_docs/version-2.4.0/widgets/color-picker.md @@ -81,4 +81,20 @@ Any property having `Fx` button next to its field can be **programmatically conf ToolJet - Widget Reference - Color Picker -
\ No newline at end of file +
+ +## 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')` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/container.md b/docs/versioned_docs/version-2.4.0/widgets/container.md index fc58141f51..8fe5d3506a 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/container.md +++ b/docs/versioned_docs/version-2.4.0/widgets/container.md @@ -57,4 +57,12 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/custom-component.md b/docs/versioned_docs/version-2.4.0/widgets/custom-component.md index 385252605d..953e8dcd83 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/custom-component.md +++ b/docs/versioned_docs/version-2.4.0/widgets/custom-component.md @@ -103,3 +103,14 @@ Under the General 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/date-range-picker.md b/docs/versioned_docs/version-2.4.0/widgets/date-range-picker.md index 642c97b427..0b27cf4bca 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/date-range-picker.md +++ b/docs/versioned_docs/version-2.4.0/widgets/date-range-picker.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/datepicker.md b/docs/versioned_docs/version-2.4.0/widgets/datepicker.md index 19001e9fb6..22f19e4955 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/datepicker.md +++ b/docs/versioned_docs/version-2.4.0/widgets/datepicker.md @@ -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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/divider.md b/docs/versioned_docs/version-2.4.0/widgets/divider.md index b027ae96d2..469b6626d0 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/divider.md +++ b/docs/versioned_docs/version-2.4.0/widgets/divider.md @@ -53,4 +53,13 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/dropdown.md b/docs/versioned_docs/version-2.4.0/widgets/dropdown.md index e4cdca4a72..9bd7ee5a9b 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/dropdown.md +++ b/docs/versioned_docs/version-2.4.0/widgets/dropdown.md @@ -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 | \ No newline at end of file +| 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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/file-picker.md b/docs/versioned_docs/version-2.4.0/widgets/file-picker.md index 6d7efd4219..380d2c9a65 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/file-picker.md +++ b/docs/versioned_docs/version-2.4.0/widgets/file-picker.md @@ -165,8 +165,15 @@ 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 -| Action | Description | Properties | -| ----------- | ----------- | ------------ | -| `clearFiles()` | It will clear the selected files | None | +## Exposed Variables + +| 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()` | diff --git a/docs/versioned_docs/version-2.4.0/widgets/form.md b/docs/versioned_docs/version-2.4.0/widgets/form.md index 8d368d65fd..ba5f9e5e70 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/form.md +++ b/docs/versioned_docs/version-2.4.0/widgets/form.md @@ -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**. -::: \ No newline at end of file +::: + + +## 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()` | + diff --git a/docs/versioned_docs/version-2.4.0/widgets/html.md b/docs/versioned_docs/version-2.4.0/widgets/html.md index cdb523f9d7..a4f65f8734 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/html.md +++ b/docs/versioned_docs/version-2.4.0/widgets/html.md @@ -70,3 +70,12 @@ Under the General 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/icon.md b/docs/versioned_docs/version-2.4.0/widgets/icon.md index 9cd9fe3bd9..168824e22d 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/icon.md +++ b/docs/versioned_docs/version-2.4.0/widgets/icon.md @@ -62,3 +62,17 @@ 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()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/iframe.md b/docs/versioned_docs/version-2.4.0/widgets/iframe.md index 3157b9f979..3fdaf72c04 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/iframe.md +++ b/docs/versioned_docs/version-2.4.0/widgets/iframe.md @@ -64,4 +64,13 @@ 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**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/image.md b/docs/versioned_docs/version-2.4.0/widgets/image.md index f84ee6f9c3..6ca6250b6c 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/image.md +++ b/docs/versioned_docs/version-2.4.0/widgets/image.md @@ -104,4 +104,13 @@ 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**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/kanban-board.md b/docs/versioned_docs/version-2.4.0/widgets/kanban-board.md index 0dbb693192..5f55a31b80 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/kanban-board.md +++ b/docs/versioned_docs/version-2.4.0/widgets/kanban-board.md @@ -118,6 +118,7 @@ Under the General accordion, you can set the value in the string format. | Visibility | This is to control the visibility of the widget. If `{{false}}`/disabled the widget will not visible after the app is deployed. By default, it's enabled (set to `{{true}}`). | | Accent color | You can change the accent color of the column title by entering the Hex color code or choosing a color of your choice from the color picker. | + ## Exposed variables
@@ -134,4 +135,15 @@ Under the General 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}}` | \ No newline at end of file +| 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')` | diff --git a/docs/versioned_docs/version-2.4.0/widgets/link.md b/docs/versioned_docs/version-2.4.0/widgets/link.md index ca20002102..de7628dbab 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/link.md +++ b/docs/versioned_docs/version-2.4.0/widgets/link.md @@ -65,4 +65,16 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/listview.md b/docs/versioned_docs/version-2.4.0/widgets/listview.md index 41a8d42385..288401f339 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/listview.md +++ b/docs/versioned_docs/version-2.4.0/widgets/listview.md @@ -137,4 +137,15 @@ 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. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/map.md b/docs/versioned_docs/version-2.4.0/widgets/map.md index 2469048e68..97b8764cc8 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/map.md +++ b/docs/versioned_docs/version-2.4.0/widgets/map.md @@ -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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/modal.md b/docs/versioned_docs/version-2.4.0/widgets/modal.md index eb196123ac..02f645e79e 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/modal.md +++ b/docs/versioned_docs/version-2.4.0/widgets/modal.md @@ -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. -::: \ No newline at end of file +::: + +## 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()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/multiselect.md b/docs/versioned_docs/version-2.4.0/widgets/multiselect.md index cdc83f7ab9..8a560a4180 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/multiselect.md +++ b/docs/versioned_docs/version-2.4.0/widgets/multiselect.md @@ -75,15 +75,21 @@ Toggle on or off to control the visibility of the widget. You can programmatical 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}}`. -## Actions -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| `selectOption` | Select options. | pass options as parameter. ex: `components.multiselect1.selectOption(1)` | -| `deselectOption` | Deselect options.| pass options as parameter. ex: `components.multiselect1.deselectOption(1)` | -| `clearSelections` | Clear all selection. | ex: `components.multiselect1.clearSelections()` | +## 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]}}` | -:::info -Any property having `Fx` button next to its field can be **programmatically configured**. -::: +## 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)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/number-input.md b/docs/versioned_docs/version-2.4.0/widgets/number-input.md index f72242234c..115a1a37d1 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/number-input.md +++ b/docs/versioned_docs/version-2.4.0/widgets/number-input.md @@ -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**. -::: \ No newline at end of file +::: + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/pagination.md b/docs/versioned_docs/version-2.4.0/widgets/pagination.md index 2ee0003cc5..dae8985980 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/pagination.md +++ b/docs/versioned_docs/version-2.4.0/widgets/pagination.md @@ -67,3 +67,14 @@ 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/password-input.md b/docs/versioned_docs/version-2.4.0/widgets/password-input.md index 019ec8fae3..b692c861a1 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/password-input.md +++ b/docs/versioned_docs/version-2.4.0/widgets/password-input.md @@ -79,4 +79,15 @@ 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**. -::: \ No newline at end of file +::: + + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/pdf.md b/docs/versioned_docs/version-2.4.0/widgets/pdf.md index bc665697fe..6af77f190f 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/pdf.md +++ b/docs/versioned_docs/version-2.4.0/widgets/pdf.md @@ -54,3 +54,12 @@ Under the General 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/qr-scanner.md b/docs/versioned_docs/version-2.4.0/widgets/qr-scanner.md index edab5ade23..db4bd789ef 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/qr-scanner.md +++ b/docs/versioned_docs/version-2.4.0/widgets/qr-scanner.md @@ -69,3 +69,8 @@ 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/radio-button.md b/docs/versioned_docs/version-2.4.0/widgets/radio-button.md index 9de2d08c16..758a4dad27 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/radio-button.md +++ b/docs/versioned_docs/version-2.4.0/widgets/radio-button.md @@ -94,8 +94,15 @@ This is `off` by default, toggle `on` the switch to lock the widget and make it
-## Actions -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| selectOption | Select an option from the radio buttons. | `option` eg: `component.radiobutton1.selectOption('one')` | \ No newline at end of file +## Exposed variables + +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')` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/range-slider.md b/docs/versioned_docs/version-2.4.0/widgets/range-slider.md index 5c58220496..6a833e7571 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/range-slider.md +++ b/docs/versioned_docs/version-2.4.0/widgets/range-slider.md @@ -74,3 +74,14 @@ 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/rich-text-editor.md b/docs/versioned_docs/version-2.4.0/widgets/rich-text-editor.md index 2267259807..96449ca498 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/rich-text-editor.md +++ b/docs/versioned_docs/version-2.4.0/widgets/rich-text-editor.md @@ -54,3 +54,14 @@ 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 + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever a user enters a value in the rich text editor component. You can access the value dynamically using JS: `{{components.richtexteditor1.value}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.4.0/widgets/spinner.md b/docs/versioned_docs/version-2.4.0/widgets/spinner.md index 8557673342..7869b13be0 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/spinner.md +++ b/docs/versioned_docs/version-2.4.0/widgets/spinner.md @@ -46,4 +46,12 @@ Change the color of the Spinner by entering the `Hex color code` or choosing a c ### Size -Change the size of the Spinner by selecting options from the dropdown. It has small and large sizes available. \ No newline at end of file +Change the size of the Spinner by selecting options from the dropdown. It has small and large sizes available. + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/star-rating.md b/docs/versioned_docs/version-2.4.0/widgets/star-rating.md index d204438589..39040129a0 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/star-rating.md +++ b/docs/versioned_docs/version-2.4.0/widgets/star-rating.md @@ -83,3 +83,14 @@ 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 + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever a rating is added on the component. The variable holds a numerical value. You can access the value dynamically using JS: `{{components.colorpicker1.selectedColorHex}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.4.0/widgets/statistics.md b/docs/versioned_docs/version-2.4.0/widgets/statistics.md index b2fa668f13..62cd3f8e2b 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/statistics.md +++ b/docs/versioned_docs/version-2.4.0/widgets/statistics.md @@ -91,3 +91,11 @@ Toggle on or off to control the visibility of the widget. You can programmatical :::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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/steps.md b/docs/versioned_docs/version-2.4.0/widgets/steps.md index 5c486e0ecb..f43628f9c3 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/steps.md +++ b/docs/versioned_docs/version-2.4.0/widgets/steps.md @@ -89,4 +89,14 @@ Toggle on or off to control the visibility of the widget. You can programmatical :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| currentStepId | This variable holds the id of the currently selected step on the step component. You can access the value dynamically using JS: `{{components.steps1.currentStepId}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.4.0/widgets/svg-image.md b/docs/versioned_docs/version-2.4.0/widgets/svg-image.md index 76500c1eb8..b3a7a5ac7f 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/svg-image.md +++ b/docs/versioned_docs/version-2.4.0/widgets/svg-image.md @@ -56,4 +56,12 @@ Toggle on or off to display the widget in mobile view. You can programmatically ### 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}}`. \ No newline at end of file +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}}`. + +## 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. \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/table.md b/docs/versioned_docs/version-2.4.0/widgets/table.md index 15b05f6fbd..93f9c5dff2 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/table.md +++ b/docs/versioned_docs/version-2.4.0/widgets/table.md @@ -327,3 +327,16 @@ This event is triggered when filter is added, removed, or updated from the filte :::info Any property having `Fx` button next to its field can be **programmatically configured**. ::: + + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setPage | Set the page on the table via 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.table1.setPage(2)` | +| selectRow | Select the row on the table using via 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.table1.selectRow('id','11')` | +| deselectRow | Deselect the row on the table via 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.table1.deselectRow()` | +| discardChanges | Discard the changes from the table when a cell is edited via 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.table1.discardChanges()` | +| discardNewlyAddedRows | Discard the newly added rows from the add new row popup on the table via 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.table1.discardNewlyAddedRows()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/tabs.md b/docs/versioned_docs/version-2.4.0/widgets/tabs.md index 0bfa2a8a2a..60930903ce 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/tabs.md +++ b/docs/versioned_docs/version-2.4.0/widgets/tabs.md @@ -113,4 +113,18 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| currentTab | This variable holds the id of the current tab selected on the tabs component. You can access the value dynamically using JS: `{{components.tabs1.currentTab}}`| + +## Component specific actions (CSA) + +Following actions of Tabs component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setTab | Set the current tab of the tabs 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.tabs1.setTab(1)` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/tags.md b/docs/versioned_docs/version-2.4.0/widgets/tags.md index ea01906c69..468a37f05d 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/tags.md +++ b/docs/versioned_docs/version-2.4.0/widgets/tags.md @@ -57,4 +57,12 @@ Toggle on or off to display the widget in mobile view. You can programmatically ### 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}}`. \ No newline at end of file +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}}`. + +## 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/text-input.md b/docs/versioned_docs/version-2.4.0/widgets/text-input.md index 17bf8d9003..6e02dcd90d 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/text-input.md +++ b/docs/versioned_docs/version-2.4.0/widgets/text-input.md @@ -109,3 +109,22 @@ This property only accepts boolean values. If set to `{{true}}`, the widget will :::info Check the **component specific actions** available for this component **[here](/docs/actions/control-component)**. ::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value whenever user a user inputs a value in the component. You can access the value dynamically using JS: `{{components.textinput1.value}}`| + +## Component specific actions (CSA) + +Following actions of text input component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setFocus | Set the focus of the cursor on the text input 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.textinput1.setFocus()` | +| setBlur | Removes the focus of the cursor on the text input 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.textinput1.setBlur()` | +| disable | disable the 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.textinput1.disable(true)` | +| visibility | Set a visibility of the text input 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.textinput1.visibility(false)` | +| setText | Set a text value on the text input 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.textinput1.setText('this is input text')` | +| clear | Clear the entered text from the text input 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.textinput1.clear()` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/text.md b/docs/versioned_docs/version-2.4.0/widgets/text.md index 8b526f6269..6f64632db3 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/text.md +++ b/docs/versioned_docs/version-2.4.0/widgets/text.md @@ -74,4 +74,19 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| text | 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}}`| + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| visibility | Set a visibility of the text 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.text1.visibility(false)` | +| setText | Set a text value on the text 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.text1.setText('this is a text')` | \ No newline at end of file diff --git a/docs/versioned_docs/version-2.4.0/widgets/textarea.md b/docs/versioned_docs/version-2.4.0/widgets/textarea.md index 8ff2513929..8b46c23805 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/textarea.md +++ b/docs/versioned_docs/version-2.4.0/widgets/textarea.md @@ -58,12 +58,21 @@ This is to control the visibility of the widget. If `{{false}}` the widget will This property only accepts boolean values. If set to `{{true}}`, the widget will be locked and becomes non-functional. By default, its value is set to `{{false}}`. -## Actions -| Action | Description | Properties | -| ----------- | ----------- | ------------------ | -| setText | Set the text. | `text` | -| clear | Clear the text. | | +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value of the text area component. You can access the value dynamically using JS: `{{components.textarea1.value}}`| + +## Component specific actions (CSA) + +Following actions of color picker component can be controlled using the component specific actions(CSA): + +| Actions | Description | +| ----------- | ----------- | +| setText | Set the text on the text area 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.textarea1.setText('this is a text')` | +| clear | clear the value from the text area 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.text1.clear()` | :::info Any property having `Fx` button next to its field can be **programmatically configured**. diff --git a/docs/versioned_docs/version-2.4.0/widgets/timeline.md b/docs/versioned_docs/version-2.4.0/widgets/timeline.md index f2d2bdd7e2..ee6cc8b819 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/timeline.md +++ b/docs/versioned_docs/version-2.4.0/widgets/timeline.md @@ -61,3 +61,12 @@ Under the General 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. diff --git a/docs/versioned_docs/version-2.4.0/widgets/timer.md b/docs/versioned_docs/version-2.4.0/widgets/timer.md index 18ba85b865..ec7c14a23d 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/timer.md +++ b/docs/versioned_docs/version-2.4.0/widgets/timer.md @@ -77,4 +77,13 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the value of the timer in the following keys: **hour**, **minute**, **second**, and **mSecond**. You can access the value dynamically using JS: `{{components.timer1.value.second}}`| +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.4.0/widgets/toggle-switch.md b/docs/versioned_docs/version-2.4.0/widgets/toggle-switch.md index 56e2ed715d..4d11ed9ecf 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/toggle-switch.md +++ b/docs/versioned_docs/version-2.4.0/widgets/toggle-switch.md @@ -73,4 +73,14 @@ 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**. -::: \ No newline at end of file +::: + +## Exposed Variables + +| Variables | Description | +| ----------- | ----------- | +| value | This variable holds the boolean value i.e `true` or `false` when the toggle is on or off respectively. You can access the value dynamically using JS: `{{components.toggleswitch1.value}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.4.0/widgets/tree-select.md b/docs/versioned_docs/version-2.4.0/widgets/tree-select.md index 64b72798a1..5bccb5c366 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/tree-select.md +++ b/docs/versioned_docs/version-2.4.0/widgets/tree-select.md @@ -155,3 +155,16 @@ 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 | +| ----------- | ----------- | +| checked | This variable holds the value of all the checked items on the tree select component. You can access the value dynamically using JS: `{{components.treeselect1.checked[1]}}`| +| expanded | This variable holds the value of expanded items on the tree select component. You can access the value dynamically using JS: `{{components.treeselect1.expanded[0]}}`| +| checkedPathArray | This variable holds the path of the checked items in differet arrays. You can access the value dynamically using JS: `{{components.treeselect1.checkedPathArray[1][1]}}`| +| checkedPathStrings | This variable holds the path of the checked items in strings separated by a dash(-). You can access the value dynamically using JS: `{{components.treeselect1.checkedPathStrings[2]}}`| + +## Component specific actions (CSA) + +There are currently no CSA (Component-Specific Actions) implemented to regulate or control the component. diff --git a/docs/versioned_docs/version-2.4.0/widgets/vertical-divider.md b/docs/versioned_docs/version-2.4.0/widgets/vertical-divider.md index 8cf2894a1c..769920cd73 100644 --- a/docs/versioned_docs/version-2.4.0/widgets/vertical-divider.md +++ b/docs/versioned_docs/version-2.4.0/widgets/vertical-divider.md @@ -54,4 +54,12 @@ Under the General accordion, you can set the value in the string format. :::info Any property having `Fx` button next to its field can be **programmatically configured**. -::: \ No newline at end of file +::: + +## 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. diff --git a/docs/versioned_sidebars/version-1.x.x-sidebars.json b/docs/versioned_sidebars/version-1.x.x-sidebars.json index 0753138ba2..cbc7da86dc 100644 --- a/docs/versioned_sidebars/version-1.x.x-sidebars.json +++ b/docs/versioned_sidebars/version-1.x.x-sidebars.json @@ -208,11 +208,13 @@ }, "items": [ "how-to/run-actions-from-runjs", + "how-to/intentionally-fail-js-query", "how-to/run-query-at-specified-intervals", "how-to/bulk-update-multiple-rows", "how-to/access-cellvalue-rowdata", "how-to/access-currentuser", "how-to/oauth2-authorization", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/upload-files-aws", "how-to/upload-files-gcs", "how-to/access-users-location" @@ -321,4 +323,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/docs/versioned_sidebars/version-2.0.0-sidebars.json b/docs/versioned_sidebars/version-2.0.0-sidebars.json index bb0aa95d25..95044555c7 100644 --- a/docs/versioned_sidebars/version-2.0.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.0.0-sidebars.json @@ -289,10 +289,12 @@ "how-to/bulk-update-multiple-rows", "how-to/access-currentuser", "how-to/use-axios-in-runjs", + "how-to/intentionally-fail-js-query", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.1.0-sidebars.json b/docs/versioned_sidebars/version-2.1.0-sidebars.json index 54a661e6e3..6674ce70a3 100644 --- a/docs/versioned_sidebars/version-2.1.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.1.0-sidebars.json @@ -290,10 +290,12 @@ "how-to/access-currentuser", "how-to/import-external-libraries-using-runjs", "how-to/use-axios-in-runjs", + "how-to/intentionally-fail-js-query", "how-to/import-external-libraries-using-runpy", "how-to/run-actions-from-runjs", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.2.0-sidebars.json b/docs/versioned_sidebars/version-2.2.0-sidebars.json index 1342f28433..041c69982f 100644 --- a/docs/versioned_sidebars/version-2.2.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.2.0-sidebars.json @@ -290,11 +290,13 @@ "how-to/bulk-update-multiple-rows", "how-to/access-currentuser", "how-to/use-axios-in-runjs", + "how-to/intentionally-fail-js-query", "how-to/import-external-libraries-using-runpy", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.3.0-sidebars.json b/docs/versioned_sidebars/version-2.3.0-sidebars.json index 1342f28433..ca12fb1f4c 100644 --- a/docs/versioned_sidebars/version-2.3.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.3.0-sidebars.json @@ -293,8 +293,10 @@ "how-to/import-external-libraries-using-runpy", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", + "how-to/intentionally-fail-js-query", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/s3-custom-endpoints", "how-to/oauth2-authorization", "how-to/upload-files-aws", diff --git a/docs/versioned_sidebars/version-2.4.0-sidebars.json b/docs/versioned_sidebars/version-2.4.0-sidebars.json index 96bdc2e3c6..6fb00ee9c3 100644 --- a/docs/versioned_sidebars/version-2.4.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.4.0-sidebars.json @@ -293,9 +293,11 @@ "how-to/import-external-libraries-using-runpy", "how-to/import-external-libraries-using-runjs", "how-to/run-actions-from-runjs", + "how-to/intentionally-fail-js-query", "how-to/run-query-at-specified-intervals", "how-to/access-users-location", "how-to/s3-custom-endpoints", + "how-to/use-s3-signed-url-to-upload-docs", "how-to/oauth2-authorization", "how-to/upload-files-aws", "how-to/upload-files-gcs", @@ -367,4 +369,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/docs/yarn.lock b/docs/yarn.lock index 38d7aeb4db..d695257bf9 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1196,84 +1196,7 @@ "@docsearch/css" "3.2.1" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.1", "@docusaurus/core@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.1.tgz" - integrity sha512-Prd46TtZdiixlTl8a+h9bI5HegkfREjSNkrX2rVEwJZeziSz4ya+l7QDnbnCB2XbxEG8cveFo/F9q5lixolDtQ== - dependencies: - "@babel/core" "^7.18.6" - "@babel/generator" "^7.18.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.18.6" - "@babel/preset-env" "^7.18.6" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.18.6" - "@babel/runtime" "^7.18.6" - "@babel/runtime-corejs3" "^7.18.6" - "@babel/traverse" "^7.18.8" - "@docusaurus/cssnano-preset" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" - "@slorber/static-site-generator-webpack-plugin" "^4.0.7" - "@svgr/webpack" "^6.2.1" - autoprefixer "^10.4.7" - babel-loader "^8.2.5" - babel-plugin-dynamic-import-node "^2.3.3" - boxen "^6.2.1" - chalk "^4.1.2" - chokidar "^3.5.3" - clean-css "^5.3.0" - cli-table3 "^0.6.2" - combine-promises "^1.1.0" - commander "^5.1.0" - copy-webpack-plugin "^11.0.0" - core-js "^3.23.3" - css-loader "^6.7.1" - css-minimizer-webpack-plugin "^4.0.0" - cssnano "^5.1.12" - del "^6.1.1" - detect-port "^1.3.0" - escape-html "^1.0.3" - eta "^1.12.3" - file-loader "^6.2.0" - fs-extra "^10.1.0" - html-minifier-terser "^6.1.0" - html-tags "^3.2.0" - html-webpack-plugin "^5.5.0" - import-fresh "^3.3.0" - leven "^3.1.0" - lodash "^4.17.21" - mini-css-extract-plugin "^2.6.1" - postcss "^8.4.14" - postcss-loader "^7.0.0" - prompts "^2.4.2" - react-dev-utils "^12.0.1" - react-helmet-async "^1.3.0" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" - react-loadable-ssr-addon-v5-slorber "^1.0.1" - react-router "^5.3.3" - react-router-config "^5.1.1" - react-router-dom "^5.3.3" - rtl-detect "^1.0.4" - semver "^7.3.7" - serve-handler "^6.1.3" - shelljs "^0.8.5" - terser-webpack-plugin "^5.3.3" - tslib "^2.4.0" - update-notifier "^5.1.0" - url-loader "^4.1.1" - wait-on "^6.0.1" - webpack "^5.73.0" - webpack-bundle-analyzer "^4.5.0" - webpack-dev-server "^4.9.3" - webpack-merge "^5.8.0" - webpackbar "^5.0.2" - -"@docusaurus/core@2.4.0": +"@docusaurus/core@2.4.0", "@docusaurus/core@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.4.0.tgz#a12c175cb2e5a7e4582e65876a50813f6168913d" integrity sha512-J55/WEoIpRcLf3afO5POHPguVZosKmJEQWKBL+K7TAnfuE7i+Y0NPLlkKtnWCehagGsgTqClfQEexH/UT4kELA== @@ -1350,16 +1273,6 @@ webpack-merge "^5.8.0" webpackbar "^5.0.2" -"@docusaurus/cssnano-preset@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.1.tgz" - integrity sha512-MCJ6rRmlqLmlCsZIoIxOxDb0rYzIPEm9PYpsBW+CGNnbk+x8xK+11hnrxzvXHqDRNpxrq3Kq2jYUmg/DkqE6vg== - dependencies: - cssnano-preset-advanced "^5.3.8" - postcss "^8.4.14" - postcss-sort-media-queries "^4.2.1" - tslib "^2.4.0" - "@docusaurus/cssnano-preset@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.0.tgz#9213586358e0cce517f614af041eb7d184f8add6" @@ -1370,14 +1283,6 @@ postcss-sort-media-queries "^4.2.1" tslib "^2.4.0" -"@docusaurus/logger@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.0.1.tgz" - integrity sha512-wIWseCKko1w/WARcDjO3N/XoJ0q/VE42AthP0eNAfEazDjJ94NXbaI6wuUsuY/bMg6hTKGVIpphjj2LoX3g6dA== - dependencies: - chalk "^4.1.2" - tslib "^2.4.0" - "@docusaurus/logger@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.4.0.tgz#393d91ad9ecdb9a8f80167dd6a34d4b45219b835" @@ -1386,29 +1291,6 @@ chalk "^4.1.2" tslib "^2.4.0" -"@docusaurus/mdx-loader@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.1.tgz" - integrity sha512-tdNeljdilXCmhbaEND3SAgsqaw/oh7v9onT5yrIrL26OSk2AFwd+MIi4R8jt8vq33M0R4rz2wpknm0fQIkDdvQ== - dependencies: - "@babel/parser" "^7.18.8" - "@babel/traverse" "^7.18.8" - "@docusaurus/logger" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@mdx-js/mdx" "^1.6.22" - escape-html "^1.0.3" - file-loader "^6.2.0" - fs-extra "^10.1.0" - image-size "^1.0.1" - mdast-util-to-string "^2.0.0" - remark-emoji "^2.2.0" - stringify-object "^3.3.0" - tslib "^2.4.0" - unified "^9.2.2" - unist-util-visit "^2.0.3" - url-loader "^4.1.1" - webpack "^5.73.0" - "@docusaurus/mdx-loader@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.4.0.tgz#c6310342904af2f203e7df86a9df623f86840f2d" @@ -1432,13 +1314,13 @@ url-loader "^4.1.1" webpack "^5.73.0" -"@docusaurus/module-type-aliases@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.1.tgz" - integrity sha512-f888ylnxHAM/3T8p1lx08+lTc6/g7AweSRfRuZvrVhHXj3Tz/nTTxaP6gPTGkJK7WLqTagpar/IGP6/74IBbkg== +"@docusaurus/module-type-aliases@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.0.tgz#6961605d20cd46f86163ed8c2d83d438b02b4028" + integrity sha512-YEQO2D3UXs72qCn8Cr+RlycSQXVGN9iEUyuHwTuK4/uL/HFomB2FHSU0vSDM23oLd+X/KibQ3Ez6nGjQLqXcHg== dependencies: "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/types" "2.0.1" + "@docusaurus/types" "2.4.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1446,18 +1328,18 @@ react-helmet-async "*" react-loadable "npm:@docusaurus/react-loadable@5.5.2" -"@docusaurus/plugin-content-blog@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.1.tgz" - integrity sha512-/4ua3iFYcpwgpeYgHnhVGROB/ybnauLH2+rICb4vz/+Gn1hjAmGXVYq1fk8g49zGs3uxx5nc0H5bL9P0g977IQ== +"@docusaurus/plugin-content-blog@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.0.tgz#50dbfbc7b51f152ae660385fd8b34076713374c3" + integrity sha512-YwkAkVUxtxoBAIj/MCb4ohN0SCtHBs4AS75jMhPpf67qf3j+U/4n33cELq7567hwyZ6fMz2GPJcVmctzlGGThQ== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/logger" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-common" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" cheerio "^1.0.0-rc.12" feed "^4.2.2" fs-extra "^10.1.0" @@ -1468,18 +1350,18 @@ utility-types "^3.10.0" webpack "^5.73.0" -"@docusaurus/plugin-content-docs@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.1.tgz" - integrity sha512-2qeBWRy1EjgnXdwAO6/csDIS1UVNmhmtk/bQ2s9jqjpwM8YVgZ8QVdkxFAMWXgZWDQdwWwdP1rnmoEelE4HknQ== +"@docusaurus/plugin-content-docs@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.0.tgz#36e235adf902325735b873b4f535205884363728" + integrity sha512-ic/Z/ZN5Rk/RQo+Io6rUGpToOtNbtPloMR2JcGwC1xT2riMu6zzfSwmBi9tHJgdXH6CB5jG+0dOZZO8QS5tmDg== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/module-type-aliases" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/logger" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/module-type-aliases" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" "@types/react-router-config" "^5.0.6" combine-promises "^1.1.0" fs-extra "^10.1.0" @@ -1490,68 +1372,63 @@ utility-types "^3.10.0" webpack "^5.73.0" -"@docusaurus/plugin-content-pages@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.1.tgz" - integrity sha512-6apSVeJENnNecAH5cm5VnRqR103M6qSI6IuiP7tVfD5H4AWrfDNkvJQV2+R2PIq3bGrwmX4fcXl1x4g0oo7iwA== +"@docusaurus/plugin-content-pages@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.0.tgz#6169909a486e1eae0ddffff0b1717ce4332db4d4" + integrity sha512-Pk2pOeOxk8MeU3mrTU0XLIgP9NZixbdcJmJ7RUFrZp1Aj42nd0RhIT14BGvXXyqb8yTQlk4DmYGAzqOfBsFyGw== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" fs-extra "^10.1.0" tslib "^2.4.0" webpack "^5.73.0" -"@docusaurus/plugin-debug@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.1.tgz" - integrity sha512-jpZBT5HK7SWx1LRQyv9d14i44vSsKXGZsSPA2ndth5HykHJsiAj9Fwl1AtzmtGYuBmI+iXQyOd4MAMHd4ZZ1tg== +"@docusaurus/plugin-debug@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.4.0.tgz#1ad513fe9bcaf017deccf62df8b8843faeeb7d37" + integrity sha512-KC56DdYjYT7Txyux71vXHXGYZuP6yYtqwClvYpjKreWIHWus5Zt6VNi23rMZv3/QKhOCrN64zplUbdfQMvddBQ== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" fs-extra "^10.1.0" react-json-view "^1.21.3" tslib "^2.4.0" -"@docusaurus/plugin-google-analytics@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.1.tgz" - integrity sha512-d5qb+ZeQcg1Czoxc+RacETjLdp2sN/TAd7PGN/GrvtijCdgNmvVAtZ9QgajBTG0YbJFVPTeZ39ad2bpoOexX0w== +"@docusaurus/plugin-google-analytics@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.0.tgz#8062d7a09d366329dfd3ce4e8a619da8624b6cc3" + integrity sha512-uGUzX67DOAIglygdNrmMOvEp8qG03X20jMWadeqVQktS6nADvozpSLGx4J0xbkblhJkUzN21WiilsP9iVP+zkw== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" tslib "^2.4.0" -"@docusaurus/plugin-google-gtag@2.0.1", "@docusaurus/plugin-google-gtag@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.1.tgz" - integrity sha512-qiRufJe2FvIyzICbkjm4VbVCI1hyEju/CebfDKkKh2ZtV4q6DM1WZG7D6VoQSXL8MrMFB895gipOM4BwdM8VsQ== +"@docusaurus/plugin-google-gtag@2.4.0", "@docusaurus/plugin-google-gtag@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.0.tgz#a8efda476f971410dfb3aab1cfe1f0f7d269adc5" + integrity sha512-adj/70DANaQs2+TF/nRdMezDXFAV/O/pjAbUgmKBlyOTq5qoMe0Tk4muvQIwWUmiUQxFJe+sKlZGM771ownyOg== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" tslib "^2.4.0" -"@docusaurus/plugin-sitemap@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.1.tgz" - integrity sha512-KcYuIUIp2JPzUf+Xa7W2BSsjLgN1/0h+VAz7D/C3RYjAgC5ApPX8wO+TECmGfunl/m7WKGUmLabfOon/as64kQ== +"@docusaurus/plugin-google-tag-manager@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.0.tgz#9a94324ac496835fc34e233cc60441df4e04dfdd" + integrity sha512-E66uGcYs4l7yitmp/8kMEVQftFPwV9iC62ORh47Veqzs6ExwnhzBkJmwDnwIysHBF1vlxnzET0Fl2LfL5fRR3A== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" - fs-extra "^10.1.0" - sitemap "^7.1.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" tslib "^2.4.0" -"@docusaurus/plugin-sitemap@^2.2.0": +"@docusaurus/plugin-sitemap@2.4.0", "@docusaurus/plugin-sitemap@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.0.tgz#ba0eb43565039fe011bdd874b5c5d7252b19d709" integrity sha512-pZxh+ygfnI657sN8a/FkYVIAmVv0CGk71QMKqJBOfMmDHNN1FeDeFkBjWP49ejBqpqAhjufkv5UWq3UOu2soCw== @@ -1566,23 +1443,24 @@ sitemap "^7.1.1" tslib "^2.4.0" -"@docusaurus/preset-classic@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.1.tgz" - integrity sha512-nOoniTg46My1qdDlLWeFs55uEmxOJ+9WMF8KKG8KMCu5LAvpemMi7rQd4x8Tw+xiPHZ/sQzH9JmPTMPRE4QGPw== +"@docusaurus/preset-classic@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.4.0.tgz#92fdcfab35d8d0ffb8c38bcbf439e4e1cb0566a3" + integrity sha512-/5z5o/9bc6+P5ool2y01PbJhoGddEGsC0ej1MF6mCoazk8A+kW4feoUd68l7Bnv01rCnG3xy7kHUQP97Y0grUA== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/plugin-content-blog" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/plugin-content-pages" "2.0.1" - "@docusaurus/plugin-debug" "2.0.1" - "@docusaurus/plugin-google-analytics" "2.0.1" - "@docusaurus/plugin-google-gtag" "2.0.1" - "@docusaurus/plugin-sitemap" "2.0.1" - "@docusaurus/theme-classic" "2.0.1" - "@docusaurus/theme-common" "2.0.1" - "@docusaurus/theme-search-algolia" "2.0.1" - "@docusaurus/types" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/plugin-content-blog" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/plugin-content-pages" "2.4.0" + "@docusaurus/plugin-debug" "2.4.0" + "@docusaurus/plugin-google-analytics" "2.4.0" + "@docusaurus/plugin-google-gtag" "2.4.0" + "@docusaurus/plugin-google-tag-manager" "2.4.0" + "@docusaurus/plugin-sitemap" "2.4.0" + "@docusaurus/theme-classic" "2.4.0" + "@docusaurus/theme-common" "2.4.0" + "@docusaurus/theme-search-algolia" "2.4.0" + "@docusaurus/types" "2.4.0" "@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": version "5.5.2" @@ -1592,27 +1470,27 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.1.tgz" - integrity sha512-0jfigiqkUwIuKOw7Me5tqUM9BBvoQX7qqeevx7v4tkYQexPhk3VYSZo7aRuoJ9oyW5makCTPX551PMJzmq7+sw== +"@docusaurus/theme-classic@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.4.0.tgz#a5404967b00adec3472efca4c3b3f6a5e2021c78" + integrity sha512-GMDX5WU6Z0OC65eQFgl3iNNEbI9IMJz9f6KnOyuMxNUR6q0qVLsKCNopFUDfFNJ55UU50o7P7o21yVhkwpfJ9w== dependencies: - "@docusaurus/core" "2.0.1" - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/module-type-aliases" "2.0.1" - "@docusaurus/plugin-content-blog" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/plugin-content-pages" "2.0.1" - "@docusaurus/theme-common" "2.0.1" - "@docusaurus/theme-translations" "2.0.1" - "@docusaurus/types" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-common" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/module-type-aliases" "2.4.0" + "@docusaurus/plugin-content-blog" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/plugin-content-pages" "2.4.0" + "@docusaurus/theme-common" "2.4.0" + "@docusaurus/theme-translations" "2.4.0" + "@docusaurus/types" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-common" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" "@mdx-js/react" "^1.6.22" clsx "^1.2.1" copy-text-to-clipboard "^3.0.1" - infima "0.2.0-alpha.42" + infima "0.2.0-alpha.43" lodash "^4.17.21" nprogress "^0.2.0" postcss "^8.4.14" @@ -1623,17 +1501,18 @@ tslib "^2.4.0" utility-types "^3.10.0" -"@docusaurus/theme-common@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.0.1.tgz" - integrity sha512-I3b6e/ryiTQMsbES40cP0DRGnfr0E2qghVq+XecyMKjBPejISoSFEDn0MsnbW8Q26k1Dh/0qDH8QKDqaZZgLhA== +"@docusaurus/theme-common@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.4.0.tgz#626096fe9552d240a2115b492c7e12099070cf2d" + integrity sha512-IkG/l5f/FLY6cBIxtPmFnxpuPzc5TupuqlOx+XDN+035MdQcAh8wHXXZJAkTeYDeZ3anIUSUIvWa7/nRKoQEfg== dependencies: - "@docusaurus/mdx-loader" "2.0.1" - "@docusaurus/module-type-aliases" "2.0.1" - "@docusaurus/plugin-content-blog" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/plugin-content-pages" "2.0.1" - "@docusaurus/utils" "2.0.1" + "@docusaurus/mdx-loader" "2.4.0" + "@docusaurus/module-type-aliases" "2.4.0" + "@docusaurus/plugin-content-blog" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/plugin-content-pages" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-common" "2.4.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1641,52 +1520,39 @@ parse-numeric-range "^1.3.0" prism-react-renderer "^1.3.5" tslib "^2.4.0" + use-sync-external-store "^1.2.0" utility-types "^3.10.0" -"@docusaurus/theme-search-algolia@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.1.tgz" - integrity sha512-cw3NaOSKbYlsY6uNj4PgO+5mwyQ3aEWre5RlmvjStaz2cbD15Nr69VG8Rd/F6Q5VsCT8BvSdkPDdDG5d/ACexg== +"@docusaurus/theme-search-algolia@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.0.tgz#07d297d50c44446d6bc5a37be39afb8f014084e1" + integrity sha512-pPCJSCL1Qt4pu/Z0uxBAuke0yEBbxh0s4fOvimna7TEcBLPq0x06/K78AaABXrTVQM6S0vdocFl9EoNgU17hqA== dependencies: "@docsearch/react" "^3.1.1" - "@docusaurus/core" "2.0.1" - "@docusaurus/logger" "2.0.1" - "@docusaurus/plugin-content-docs" "2.0.1" - "@docusaurus/theme-common" "2.0.1" - "@docusaurus/theme-translations" "2.0.1" - "@docusaurus/utils" "2.0.1" - "@docusaurus/utils-validation" "2.0.1" + "@docusaurus/core" "2.4.0" + "@docusaurus/logger" "2.4.0" + "@docusaurus/plugin-content-docs" "2.4.0" + "@docusaurus/theme-common" "2.4.0" + "@docusaurus/theme-translations" "2.4.0" + "@docusaurus/utils" "2.4.0" + "@docusaurus/utils-validation" "2.4.0" algoliasearch "^4.13.1" algoliasearch-helper "^3.10.0" clsx "^1.2.1" - eta "^1.12.3" + eta "^2.0.0" fs-extra "^10.1.0" lodash "^4.17.21" tslib "^2.4.0" utility-types "^3.10.0" -"@docusaurus/theme-translations@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.0.1.tgz" - integrity sha512-v1MYYlbsdX+rtKnXFcIAn9ar0Z6K0yjqnCYS0p/KLCLrfJwfJ8A3oRJw2HiaIb8jQfk1WMY2h5Qi1p4vHOekQw== +"@docusaurus/theme-translations@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.4.0.tgz#62dacb7997322f4c5a828b3ab66177ec6769eb33" + integrity sha512-kEoITnPXzDPUMBHk3+fzEzbopxLD3fR5sDoayNH0vXkpUukA88/aDL1bqkhxWZHA3LOfJ3f0vJbOwmnXW5v85Q== dependencies: fs-extra "^10.1.0" tslib "^2.4.0" -"@docusaurus/types@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.1.tgz" - integrity sha512-o+4hAFWkj3sBszVnRTAnNqtAIuIW0bNaYyDwQhQ6bdz3RAPEq9cDKZxMpajsj4z2nRty8XjzhyufAAjxFTyrfg== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - commander "^5.1.0" - joi "^17.6.0" - react-helmet-async "^1.3.0" - utility-types "^3.10.0" - webpack "^5.73.0" - webpack-merge "^5.8.0" - "@docusaurus/types@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.4.0.tgz#f94f89a0253778b617c5d40ac6f16b17ec55ce41" @@ -1701,13 +1567,6 @@ webpack "^5.73.0" webpack-merge "^5.8.0" -"@docusaurus/utils-common@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.0.1.tgz" - integrity sha512-kajCCDCXRd1HFH5EUW31MPaQcsyNlGakpkDoTBtBvpa4EIPvWaSKy7TIqYKHrZjX4tnJ0YbEJvaXfjjgdq5xSg== - dependencies: - tslib "^2.4.0" - "@docusaurus/utils-common@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.4.0.tgz#eb2913871860ed32e73858b4c7787dd820c5558d" @@ -1715,17 +1574,6 @@ dependencies: tslib "^2.4.0" -"@docusaurus/utils-validation@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.1.tgz" - integrity sha512-f14AnwFBy4/1A19zWthK+Ii80YDz+4qt8oPpK3julywXsheSxPBqgsND3LVBBvB2p3rJHvbo2m3HyB9Tco1JRw== - dependencies: - "@docusaurus/logger" "2.0.1" - "@docusaurus/utils" "2.0.1" - joi "^17.6.0" - js-yaml "^4.1.0" - tslib "^2.4.0" - "@docusaurus/utils-validation@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.4.0.tgz#1ed92bfab5da321c4a4d99cad28a15627091aa90" @@ -1737,27 +1585,6 @@ js-yaml "^4.1.0" tslib "^2.4.0" -"@docusaurus/utils@2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.1.tgz" - integrity sha512-u2Vdl/eoVwMfUjDCkg7FjxoiwFs/XhVVtNxQEw8cvB+qaw6QWyT73m96VZzWtUb1fDOefHoZ+bZ0ObFeKk9lMQ== - dependencies: - "@docusaurus/logger" "2.0.1" - "@svgr/webpack" "^6.2.1" - file-loader "^6.2.0" - fs-extra "^10.1.0" - github-slugger "^1.4.0" - globby "^11.1.0" - gray-matter "^4.0.3" - js-yaml "^4.1.0" - lodash "^4.17.21" - micromatch "^4.0.5" - resolve-pathname "^3.0.0" - shelljs "^0.8.5" - tslib "^2.4.0" - url-loader "^4.1.1" - webpack "^5.73.0" - "@docusaurus/utils@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.4.0.tgz#fdf0c3545819e48bb57eafc5057495fd4d50e900" @@ -1862,7 +1689,7 @@ unist-builder "2.0.3" unist-util-visit "2.0.3" -"@mdx-js/react@^1.6.21", "@mdx-js/react@^1.6.22": +"@mdx-js/react@^1.6.22": version "1.6.22" resolved "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz" integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== @@ -3000,7 +2827,7 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -clsx@^1.1.1, clsx@^1.2.1: +clsx@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -3752,11 +3579,6 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eta@^1.12.3: - version "1.12.3" - resolved "https://registry.npmjs.org/eta/-/eta-1.12.3.tgz" - integrity sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg== - eta@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/eta/-/eta-2.0.1.tgz#199e675359cb6e19d38f29e1f405e1ba0e79a6df" @@ -4544,10 +4366,10 @@ indent-string@^4.0.0: resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -infima@0.2.0-alpha.42: - version "0.2.0-alpha.42" - resolved "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.42.tgz" - integrity sha512-ift8OXNbQQwtbIt6z16KnSWP7uJ/SysSMFI4F87MNRTicypfl4Pv3E2OGVv6N3nSZFJvA8imYulCBS64iyHYww== +infima@0.2.0-alpha.43: + version "0.2.0-alpha.43" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.43.tgz#f7aa1d7b30b6c08afef441c726bac6150228cbe0" + integrity sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ== inflight@^1.0.4: version "1.0.6" @@ -6091,9 +5913,9 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@^17.0.1: +react-dom@^17.0.2: version "17.0.2" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: loose-envify "^1.1.0" @@ -6193,9 +6015,9 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.3.0" use-latest "^1.2.1" -react@^17.0.1: +react@^17.0.2: version "17.0.2" - resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" @@ -7337,6 +7159,11 @@ use-latest@^1.2.1: dependencies: use-isomorphic-layout-effect "^1.1.1" +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index e26de5f87f..cf38665116 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -1,74 +1,80 @@ module.exports = { - env: { - browser: true, - amd: true, - es2021: true, - node: true, - 'jest/globals': true, - }, - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', - 'plugin:import/errors', - 'plugin:import/warnings', - 'plugin:prettier/recommended', + root: true, + overrides: [ + { + files: ['**/*.js', '**/*.jsx'], + env: { + browser: true, + amd: true, + es2021: true, + node: true, + 'jest/globals': true, + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:prettier/recommended', + ], + parser: '@babel/eslint-parser', + parserOptions: { + requireConfigFile: false, + babelOptions: { + configFile: __dirname + '/babel.config.js', + }, + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 12, + sourceType: 'module', + }, + plugins: ['react', 'prettier', 'jest'], + rules: { + 'prettier/prettier': [ + 'error', + { + semi: true, + trailingComma: 'es5', + printWidth: 120, + singleQuote: true, + arrowParens: 'always', + proseWrap: 'preserve', + }, + ], + 'react/prop-types': 0, + 'react/display-name': 'off', + 'no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + 'react/no-deprecated': 0, + 'no-prototype-builtins': 0, + 'jest/no-disabled-tests': 'warn', + 'jest/no-focused-tests': 'error', + 'jest/no-identical-title': 'error', + 'jest/prefer-to-have-length': 'warn', + 'jest/valid-expect': 'error', + 'import/no-unresolved': ['error', { ignore: ['^@/', 'react-hot-toast', 'react-i18next'] }], + 'react/no-unknown-property': 'off', + }, + settings: { + react: { + version: 'detect', + }, + 'import/resolver': 'webpack', + }, + globals: { + path: true, + fetch: true, + process: true, + module: true, + __dirname: true, + }, + }, ], - parser: '@babel/eslint-parser', - parserOptions: { - requireConfigFile: false, - babelOptions: { - configFile: __dirname + '/babel.config.js', - }, - ecmaFeatures: { - jsx: true, - }, - ecmaVersion: 12, - sourceType: 'module', - }, - plugins: ['react', 'prettier', 'jest'], - rules: { - 'prettier/prettier': [ - 'error', - { - semi: true, - trailingComma: 'es5', - printWidth: 120, - singleQuote: true, - arrowParens: 'always', - proseWrap: 'preserve', - }, - ], - 'react/prop-types': 0, - 'react/display-name': 'off', - 'no-unused-vars': [ - 'warn', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - }, - ], - 'react/no-deprecated': 0, - 'no-prototype-builtins': 0, - 'jest/no-disabled-tests': 'warn', - 'jest/no-focused-tests': 'error', - 'jest/no-identical-title': 'error', - 'jest/prefer-to-have-length': 'warn', - 'jest/valid-expect': 'error', - 'import/no-unresolved': ['error', { ignore: ['^@/', 'react-hot-toast', 'react-i18next'] }], - 'react/no-unknown-property': 'off', - }, - settings: { - react: { - version: 'detect', - }, - 'import/resolver': 'webpack', - }, - globals: { - path: true, - fetch: true, - process: true, - module: true, - __dirname: true, - }, }; diff --git a/frontend/assets/images/icons/app-icons/more-vertical.svg b/frontend/assets/images/icons/app-icons/more-vertical.svg new file mode 100644 index 0000000000..37acaa4623 --- /dev/null +++ b/frontend/assets/images/icons/app-icons/more-vertical.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/images/no-apps.svg b/frontend/assets/images/no-apps.svg index dc9d656f0b..ebd604e9ac 100644 --- a/frontend/assets/images/no-apps.svg +++ b/frontend/assets/images/no-apps.svg @@ -1,4 +1,4 @@ - + diff --git a/frontend/assets/images/rocket.svg b/frontend/assets/images/rocket.svg index 4f0f67c38d..16fb357cec 100644 --- a/frontend/assets/images/rocket.svg +++ b/frontend/assets/images/rocket.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/frontend/assets/translations/en.json b/frontend/assets/translations/en.json index 44e528a4b1..7118f15f3d 100644 --- a/frontend/assets/translations/en.json +++ b/frontend/assets/translations/en.json @@ -3,6 +3,7 @@ "readDocumentation": "Read documentation", "cancel": "Cancel", "save": "Save", + "savechanges": "Save changes", "back": "Back", "edit": "Edit", "search": "Search", @@ -36,7 +37,8 @@ "path": "Path", "query": "Query", "requestBody": "Request Body", - "page": "Page" + "page": "Page", + "searchItem": "Search item" }, "errorBoundary": "Something went wrong.", "viewer": "Sorry!. This app is under maintenance", @@ -228,7 +230,7 @@ "view": "View" }, "organization": { - "addNewWorkSpace":"Add new workspace", + "addNewWorkSpace": "Add new workspace", "loadOrganizations": "Load Organizations", "createWorkspace": "Create workspace", "workspaceName": "workspace name", @@ -243,18 +245,21 @@ }, "manageUsers": { "usersAndPermission": "Users & Permissions", - "inviteNewUser": "Invite new user", + "inviteNewUser": "Invite one user", + "inviteUsers": "Invite users", "name": "NAME", "email": "EMAIL", "status": "STATUS", "archive": "Archive", "unarchive": "Unarchive", - "addNewUser": "Add new user", + "addNewUser": "Add users", "emailAddress": "Email address", "createUser": "Create User", "enterFirstName": "Enter First Name", "enterLastName": "Enter Last Name", - "enterEmail": "Enter Email" + "enterEmail": "Enter email id", + "enterFulltName": "Enter full name", + "inviteNewUsers":"Invite new users" }, "manageGroups": { "permissions": { @@ -275,7 +280,7 @@ "name": "name", "addUsersToGroup": "Select users to add to the group", "email": "email", - "resource": "resource", + "resource": "Resource", "createUpdateDelete": "Create/Update/Delete", "folder": "Folder" } @@ -292,7 +297,7 @@ "loginUrl": "Login URL", "workspaceLogin": "Use this URL to login directly to this workspace", "allowDefaultSso": "Allow default SSO", - "ssoAuth": "Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO." + "ssoAuth": "Allow users to authenticate via default SSO. Default SSO configurations can be overridden by \n workspace level SSO." }, "google": { "title": "Google", @@ -355,7 +360,8 @@ "newPassword": "New password", "confirmNewPassword": "Confirm new password", "enterCurrentPassword": "Enter current password", - "enterNewPassword": "Enter new password" + "enterNewPassword": "Enter new password", + "inviteUsers": "Invite users" }, "profile": "Profile", "logout": "Logout" @@ -379,7 +385,7 @@ "blankPage": { "welcomeToToolJet": "Welcome to your new ToolJet workspace", "getStartedCreateNewApp": "You can get started by creating a new application or by creating an application using a template in ToolJet Library.", - "importApplication": "Import an application" + "importApplication": "Import an app" }, "foldersSection": { "allApplications": "All apps", @@ -394,7 +400,7 @@ "wishToDeleteFolder": "Are you sure you want to delete the folder? Apps within the folder will not be deleted." }, "header": { - "createNewApplication": "New app", + "createNewApplication": "Create new app", "import": "Import", "chooseFromTemplate": "Choose from template" }, diff --git a/frontend/ee/components/UsersPage/UsersFilter.jsx b/frontend/ee/components/UsersPage/UsersFilter.jsx index 441a456c02..231997abbc 100644 --- a/frontend/ee/components/UsersPage/UsersFilter.jsx +++ b/frontend/ee/components/UsersPage/UsersFilter.jsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import Select from '@/_ui/Select'; +import { debounce } from 'lodash'; const userStatusOptions = [ { name: 'All', value: '' }, @@ -8,102 +9,83 @@ const userStatusOptions = [ { name: 'Archived', value: 'archived' }, ]; -const UsersFilter = ({ filterList, darkMode, clearIconPressed }) => { - const [options, setOptions] = React.useState({ email: '', firstName: '', lastName: '', status: '' }); +const UsersFilter = ({ filterList }) => { + const [options, setOptions] = useState({ searchText: '', status: '' }); + const [statusVal, setStatusVal] = useState(''); + const [queryVal, setQueryVal] = useState(); - const valuesChanged = (event, key) => { + const statusValuesChanged = (event) => { let newOptions = {}; - if (!key) { - newOptions = { ...options, [event.target.name]: event.target.value }; - } else { - newOptions = { ...options, [key]: event }; - } + newOptions = { + ...options, + searchText: queryVal, + status: event, + }; setOptions(newOptions); }; - const clearTextAndResult = () => { - setOptions({ email: '', firstName: '', lastName: '', status: '' }); - clearIconPressed(); + const queryValuesChanged = (event) => { + let newOptions = {}; + newOptions = { + ...options, + status: statusVal, + searchText: event.target.value, + }; + setOptions(newOptions); }; + useEffect(() => { + const debouncedFilter = debounce(() => { + filterList(options); + }, 500); - const handleEnterKey = (e) => { - if (e.key === 'Enter') filterList(options); - }; + debouncedFilter(); + return debouncedFilter.cancel; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [options.searchText, options.status]); return ( -
-
-
- +
+
+
+
+ Showing +
+
+ -
-
- -
-
- { + setQueryVal(e.target.value); + queryValuesChanged(e); + }} + data-cy="input-field-user-filter-search" + />
+
); }; diff --git a/frontend/ee/components/UsersPage/UsersTable.jsx b/frontend/ee/components/UsersPage/UsersTable.jsx index e05252957a..6b72479090 100644 --- a/frontend/ee/components/UsersPage/UsersTable.jsx +++ b/frontend/ee/components/UsersPage/UsersTable.jsx @@ -1,9 +1,11 @@ import React from 'react'; import { CopyToClipboard } from 'react-copy-to-clipboard'; -import Avatar from '../../../src/_ui/Avatar'; +import Avatar from '@/_ui/Avatar'; import Skeleton from 'react-loading-skeleton'; import cx from 'classnames'; import { Pagination } from '@/_components'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; import { Tooltip } from 'react-tooltip'; const UsersTable = ({ @@ -21,20 +23,28 @@ const UsersTable = ({ translator, }) => { return ( -
-
-
- +
+
+
+
- - + + {users && users[0]?.status ? ( - + ) : ( )} + + {isLoading ? ( @@ -70,74 +80,76 @@ const UsersTable = ({ users.length > 0 && users.map((user) => ( - {user.status && ( )} ))} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2f1c581535..05fed25d79 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -125,7 +125,7 @@ "compression-webpack-plugin": "^10.0.0", "css-loader": "^6.7.3", "esbuild": "^0.17.8", - "eslint": "^8.34.0", + "eslint": "^8.37.0", "eslint-config-prettier": "^8.6.0", "eslint-import-resolver-webpack": "^0.13.2", "eslint-plugin-import": "^2.27.5", @@ -167,6 +167,7 @@ "@tooljet-plugins/gcs": "file:packages/gcs", "@tooljet-plugins/googlesheets": "file:packages/googlesheets", "@tooljet-plugins/graphql": "file:packages/graphql", + "@tooljet-plugins/grpc": "file:packages/grpc", "@tooljet-plugins/influxdb": "file:packages/influxdb", "@tooljet-plugins/mailgun": "file:packages/mailgun", "@tooljet-plugins/mariadb": "file:packages/mariadb", @@ -177,13 +178,11 @@ "@tooljet-plugins/n8n": "file:packages/n8n", "@tooljet-plugins/notion": "file:packages/notion", "@tooljet-plugins/openapi": "file:packages/openapi", - "@tooljet-plugins/oracledb": "file:packages/oracledb", "@tooljet-plugins/postgresql": "file:packages/postgresql", "@tooljet-plugins/redis": "file:packages/redis", "@tooljet-plugins/restapi": "file:packages/restapi", "@tooljet-plugins/rethinkdb": "file:packages/rethinkdb", "@tooljet-plugins/s3": "file:packages/s3", - "@tooljet-plugins/saphana": "file:packages/saphana", "@tooljet-plugins/sendgrid": "file:packages/sendgrid", "@tooljet-plugins/slack": "file:packages/slack", "@tooljet-plugins/smtp": "file:packages/smtp", @@ -197,7 +196,7 @@ "devDependencies": { "@types/jest": "^27.5.2", "@types/nodemailer": "^6.4.7", - "@types/oracledb": "^5.2.3", + "@types/oracledb": "^5.2.2", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "eslint": "^7.32.0", @@ -6298,28 +6297,6 @@ "version": "1.1.0", "license": "BSD-3-Clause" }, - "../plugins/node_modules/@sap/hana-client": { - "version": "2.15.22", - "hasInstallScript": true, - "license": "SEE LICENSE IN developer-license-3_1.txt", - "dependencies": { - "debug": "3.1.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "../plugins/node_modules/@sap/hana-client/node_modules/debug": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "../plugins/node_modules/@sap/hana-client/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, "../plugins/node_modules/@sendgrid/client": { "version": "7.7.0", "license": "MIT", @@ -6492,10 +6469,6 @@ "resolved": "../plugins/packages/openapi", "link": true }, - "../plugins/node_modules/@tooljet-plugins/oracledb": { - "resolved": "../plugins/packages/oracledb", - "link": true - }, "../plugins/node_modules/@tooljet-plugins/postgresql": { "resolved": "../plugins/packages/postgresql", "link": true @@ -6516,10 +6489,6 @@ "resolved": "../plugins/packages/s3", "link": true }, - "../plugins/node_modules/@tooljet-plugins/saphana": { - "resolved": "../plugins/packages/saphana", - "link": true - }, "../plugins/node_modules/@tooljet-plugins/sendgrid": { "resolved": "../plugins/packages/sendgrid", "link": true @@ -15561,13 +15530,6 @@ "node": ">= 0.8.0" } }, - "../plugins/node_modules/oracledb": { - "version": "5.3.0", - "license": "Apache-2.0", - "engines": { - "node": ">=10.16" - } - }, "../plugins/node_modules/os-homedir": { "version": "1.0.2", "license": "MIT", @@ -20437,14 +20399,12 @@ "../plugins/packages/oracledb": { "name": "@tooljet-plugins/oracledb", "version": "1.0.0", + "extraneous": true, "dependencies": { "@tooljet-plugins/common": "file:../common", "knex": "^0.95.14", "oracledb": "https://github.com/oracle/node-oracledb/releases/download/v5.3.0/oracledb-src-5.3.0.tgz", "react": "^17.0.2" - }, - "devDependencies": { - "@types/oracledb": "^5.2.2" } }, "../plugins/packages/postgresql": { @@ -20502,6 +20462,7 @@ "../plugins/packages/saphana": { "name": "@tooljet-plugins/saphana", "version": "1.0.0", + "extraneous": true, "dependencies": { "@sap/hana-client": "^2.12.22", "@tooljet-plugins/common": "file:../common", @@ -20535,9 +20496,6 @@ "@tooljet-plugins/common": "file:../common", "nodemailer": "^6.9.1", "react": "^17.0.2" - }, - "devDependencies": { - "@types/nodemailer": "^6.4.7" } }, "../plugins/packages/snowflake": { @@ -22726,14 +22684,51 @@ "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "1.4.1", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", + "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.5.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -22750,13 +22745,15 @@ }, "node_modules/@eslint/eslintrc/node_modules/argparse": { "version": "2.0.1", - "dev": true, - "license": "Python-2.0" + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -22769,8 +22766,9 @@ }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -22780,8 +22778,9 @@ }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -22789,6 +22788,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/js": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", + "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@floating-ui/core": { "version": "0.7.3", "license": "MIT" @@ -28089,8 +28097,9 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -31611,11 +31620,15 @@ } }, "node_modules/eslint": { - "version": "8.34.0", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", "dev": true, - "license": "MIT", "dependencies": { - "@eslint/eslintrc": "^1.4.1", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.37.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -31626,10 +31639,9 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", @@ -31650,7 +31662,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -32050,11 +32061,15 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.3.0", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/find-up": { @@ -32181,13 +32196,14 @@ } }, "node_modules/espree": { - "version": "9.4.1", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -32198,8 +32214,9 @@ }, "node_modules/espree/node_modules/acorn": { "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -32208,11 +32225,15 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.3.0", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -32227,9 +32248,10 @@ } }, "node_modules/esquery": { - "version": "1.4.0", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -43801,17 +43823,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/regexpu-core": { "version": "5.2.2", "license": "MIT", @@ -48828,13 +48839,38 @@ "dev": true, "optional": true }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", + "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.4.1", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.5.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -48845,10 +48881,14 @@ "dependencies": { "argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "globals": { "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -48856,6 +48896,8 @@ }, "js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -48863,10 +48905,18 @@ }, "type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } }, + "@eslint/js": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", + "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "dev": true + }, "@floating-ui/core": { "version": "0.7.3" }, @@ -51773,6 +51823,7 @@ "@tooljet-plugins/gcs": "file:packages/gcs", "@tooljet-plugins/googlesheets": "file:packages/googlesheets", "@tooljet-plugins/graphql": "file:packages/graphql", + "@tooljet-plugins/grpc": "file:packages/grpc", "@tooljet-plugins/influxdb": "file:packages/influxdb", "@tooljet-plugins/mailgun": "file:packages/mailgun", "@tooljet-plugins/mariadb": "file:packages/mariadb", @@ -51783,13 +51834,11 @@ "@tooljet-plugins/n8n": "file:packages/n8n", "@tooljet-plugins/notion": "file:packages/notion", "@tooljet-plugins/openapi": "file:packages/openapi", - "@tooljet-plugins/oracledb": "file:packages/oracledb", "@tooljet-plugins/postgresql": "file:packages/postgresql", "@tooljet-plugins/redis": "file:packages/redis", "@tooljet-plugins/restapi": "file:packages/restapi", "@tooljet-plugins/rethinkdb": "file:packages/rethinkdb", "@tooljet-plugins/s3": "file:packages/s3", - "@tooljet-plugins/saphana": "file:packages/saphana", "@tooljet-plugins/sendgrid": "file:packages/sendgrid", "@tooljet-plugins/slack": "file:packages/slack", "@tooljet-plugins/smtp": "file:packages/smtp", @@ -51801,7 +51850,7 @@ "@tooljet-plugins/zendesk": "file:packages/zendesk", "@types/jest": "^27.5.2", "@types/nodemailer": "^6.4.7", - "@types/oracledb": "^5.2.3", + "@types/oracledb": "^5.2.2", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "eslint": "^7.32.0", @@ -56430,23 +56479,6 @@ "@protobufjs/utf8": { "version": "1.1.0" }, - "@sap/hana-client": { - "version": "2.15.22", - "requires": { - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0" - } - } - }, "@sendgrid/client": { "version": "7.7.0", "requires": { @@ -56855,16 +56887,6 @@ } } }, - "@tooljet-plugins/oracledb": { - "version": "file:../plugins/packages/oracledb", - "requires": { - "@tooljet-plugins/common": "file:../common", - "@types/oracledb": "^5.2.2", - "knex": "^0.95.14", - "oracledb": "https://github.com/oracle/node-oracledb/releases/download/v5.3.0/oracledb-src-5.3.0.tgz", - "react": "^17.0.2" - } - }, "@tooljet-plugins/postgresql": { "version": "file:../plugins/packages/postgresql", "requires": { @@ -56912,14 +56934,6 @@ "rimraf": "^3.0.2" } }, - "@tooljet-plugins/saphana": { - "version": "file:../plugins/packages/saphana", - "requires": { - "@sap/hana-client": "^2.12.22", - "@tooljet-plugins/common": "file:../common", - "react": "^17.0.2" - } - }, "@tooljet-plugins/sendgrid": { "version": "file:../plugins/packages/sendgrid", "requires": { @@ -56942,7 +56956,6 @@ "version": "file:../plugins/packages/smtp", "requires": { "@tooljet-plugins/common": "file:../common", - "@types/nodemailer": "^6.4.7", "nodemailer": "^6.9.1", "react": "^17.0.2" } @@ -63049,9 +63062,6 @@ "word-wrap": "^1.2.3" } }, - "oracledb": { - "version": "5.3.0" - }, "os-homedir": { "version": "1.0.2" }, @@ -66519,6 +66529,8 @@ }, "acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "requires": {} }, @@ -68874,10 +68886,15 @@ "version": "1.0.5" }, "eslint": { - "version": "8.34.0", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.4.1", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.37.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -68888,10 +68905,9 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", @@ -68912,7 +68928,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -68961,7 +68976,9 @@ } }, "eslint-visitor-keys": { - "version": "3.3.0", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true }, "find-up": { @@ -69229,20 +69246,26 @@ "dev": true }, "espree": { - "version": "9.4.1", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", "dev": true, "requires": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.0" }, "dependencies": { "acorn": { "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true }, "eslint-visitor-keys": { - "version": "3.3.0", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true } } @@ -69251,7 +69274,9 @@ "version": "4.0.1" }, "esquery": { - "version": "1.4.0", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -76930,10 +76955,6 @@ "functions-have-names": "^1.2.2" } }, - "regexpp": { - "version": "3.2.0", - "dev": true - }, "regexpu-core": { "version": "5.2.2", "requires": { diff --git a/frontend/package.json b/frontend/package.json index 04d72e8aaf..4a57e6c18d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -120,7 +120,7 @@ "compression-webpack-plugin": "^10.0.0", "css-loader": "^6.7.3", "esbuild": "^0.17.8", - "eslint": "^8.34.0", + "eslint": "^8.37.0", "eslint-config-prettier": "^8.6.0", "eslint-import-resolver-webpack": "^0.13.2", "eslint-plugin-import": "^2.27.5", @@ -181,10 +181,6 @@ "eslintConfig": { "extends": "react-app" }, - "browserslist": { - "production": [], - "development": [] - }, "jest": { "transform": { "^.+\\.js?$": "babel-jest", @@ -208,4 +204,4 @@ "jsx" ] } -} \ No newline at end of file +} diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index 2d649e272e..922dd90af5 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -2,6 +2,7 @@ import React, { Suspense } from 'react'; // eslint-disable-next-line no-unused-vars import config from 'config'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; + import { getWorkspaceIdFromURL, appendWorkspaceId, @@ -33,6 +34,7 @@ import { VerificationSuccessInfoScreen } from '@/SuccessInfoScreen'; import '@/_styles/theme.scss'; import { AppLoader } from '@/AppLoader'; import SetupScreenSelfHost from '../SuccessInfoScreen/SetupScreenSelfHost'; +export const BreadCrumbContext = React.createContext({}); import 'react-tooltip/dist/react-tooltip.css'; const AppWrapper = (props) => { @@ -55,7 +57,9 @@ class AppComponent extends React.Component { darkMode: localStorage.getItem('darkMode') === 'true', }; } - + updateSidebarNAV = (val) => { + this.setState({ sidebarNav: val }); + }; fetchMetadata = () => { tooljetService.fetchMetaData().then((data) => { localStorage.setItem('currentVersion', data.installed_version); @@ -215,7 +219,7 @@ class AppComponent extends React.Component { }; render() { - const { updateAvailable, darkMode, currentUser } = this.state; + const { updateAvailable, darkMode } = this.state; let toastOptions = { style: { wordBreak: 'break-all', @@ -233,7 +237,8 @@ class AppComponent extends React.Component { }, }; } - + const { sidebarNav } = this.state; + const { updateSidebarNAV } = this; return ( <>
@@ -262,145 +267,149 @@ class AppComponent extends React.Component {
)} - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } - /> - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - {window.public_config?.ENABLE_TOOLJET_DB == 'true' && ( + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } + /> + } /> + } + /> + } + /> - + } /> - )} - {window.public_config?.ENABLE_MARKETPLACE_FEATURE === 'true' && ( - - + + + } /> - )} - } /> - - - - } - /> - - - - } - /> - { - if (authenticationService?.currentSessionValue?.current_organization_id) { - return ; + + + } - return ; - }} - /> - + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + {window.public_config?.ENABLE_TOOLJET_DB == 'true' && ( + + + + } + /> + )} + + {window.public_config?.ENABLE_MARKETPLACE_FEATURE === 'true' && ( + + + + } + /> + )} + } /> + + + + } + /> + + + + } + /> + { + if (authenticationService?.currentSessionValue?.current_organization_id) { + return ; + } + return ; + }} + /> + + + ); diff --git a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx index 8669c2878b..05fcdc3f7b 100644 --- a/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx +++ b/frontend/src/Editor/AppVersionsManager/CreateVersionModal.jsx @@ -63,6 +63,7 @@ export const CreateVersion = ({ setShowCreateAppVersion(false); }} title={t('editor.appVersionManager.createVersion', 'Create new version')} + checkForBackground={true} >
{ diff --git a/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx b/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx index 8710cf85bc..9a714bdfab 100644 --- a/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx +++ b/frontend/src/Editor/AppVersionsManager/EditVersionModal.jsx @@ -45,6 +45,7 @@ export const EditVersion = ({ show={showEditAppVersion} closeModal={() => setShowEditAppVersion(false)} title={t('editor.appVersionManager.editVersion', 'Edit Version')} + checkForBackground={true} > { diff --git a/frontend/src/Editor/AppVersionsManager/List.jsx b/frontend/src/Editor/AppVersionsManager/List.jsx index 2fe85dfebd..3a413a42ca 100644 --- a/frontend/src/Editor/AppVersionsManager/List.jsx +++ b/frontend/src/Editor/AppVersionsManager/List.jsx @@ -11,6 +11,7 @@ export const AppVersionsManager = function ({ setAppDefinitionFromVersion, showCreateVersionModalPrompt, closeCreateVersionModalPrompt, + onVersionDelete, }) { const [appVersions, setAppVersions] = useState([]); const [appVersionStatus, setGetAppVersionStatus] = useState(''); @@ -19,6 +20,7 @@ export const AppVersionsManager = function ({ versionName: '', showModal: false, }); + const darkMode = localStorage.getItem('darkMode') === 'true'; useEffect(() => { setGetAppVersionStatus('loading'); @@ -59,6 +61,7 @@ export const AppVersionsManager = function ({ appVersionService .del(appId, versionId) .then(() => { + onVersionDelete(); toast.dismiss(deleteingToastId); toast.success(`Version - ${versionName} Deleted`); resetDeleteModal(); @@ -84,24 +87,26 @@ export const AppVersionsManager = function ({ {appVersion.name} -
{ - e.stopPropagation(); - setDeleteVersion({ - versionId: appVersion.id, - versionName: appVersion.name, - showModal: true, - }); - }} - > - - - -
+ {appVersion.id !== releasedVersionId && ( +
{ + e.stopPropagation(); + setDeleteVersion({ + versionId: appVersion.id, + versionName: appVersion.name, + showModal: true, + }); + }} + > + + + +
+ )} ), })); @@ -128,6 +133,7 @@ export const AppVersionsManager = function ({ value={editingVersion.id} onChange={(id) => selectVersion(id)} {...customSelectProps} + className={` ${darkMode && 'dark-theme'}`} /> ); diff --git a/frontend/src/Editor/Components/Button.jsx b/frontend/src/Editor/Components/Button.jsx index 5cfa93c6ff..c6f52f6460 100644 --- a/frontend/src/Editor/Components/Button.jsx +++ b/frontend/src/Editor/Components/Button.jsx @@ -87,7 +87,7 @@ export const Button = function Button(props) { computedStyles['--tblr-btn-color-darker'] = tinycolor(backgroundColor).darken(8).toString(); } - const handleClick = (event) => { + const handleClick = () => { const event1 = new CustomEvent('submitForm', { detail: { buttonComponentId: id } }); document.dispatchEvent(event1); fireEvent('onClick'); diff --git a/frontend/src/Editor/Components/ButtonGroup.jsx b/frontend/src/Editor/Components/ButtonGroup.jsx index 0cb6b83e05..e18ef6c603 100644 --- a/frontend/src/Editor/Components/ButtonGroup.jsx +++ b/frontend/src/Editor/Components/ButtonGroup.jsx @@ -51,7 +51,8 @@ export const ButtonGroup = function Button({ } else { setData(labels); } - }, [JSON.stringify(labels), JSON.stringify(values)]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify({ labels, values })]); useEffect(() => { setDefaultActive(defaultSelected); diff --git a/frontend/src/Editor/Components/Container.jsx b/frontend/src/Editor/Components/Container.jsx index db202ca0fd..c7e687b26c 100644 --- a/frontend/src/Editor/Components/Container.jsx +++ b/frontend/src/Editor/Components/Container.jsx @@ -22,6 +22,8 @@ export const Container = function Container({ border: `1px solid ${borderColor}`, height, display: visibility ? 'flex' : 'none', + overflow: 'hidden auto', + position: 'relative', }; const parentRef = useRef(null); diff --git a/frontend/src/Editor/Components/FilePicker.jsx b/frontend/src/Editor/Components/FilePicker.jsx index 15c7389c48..3c6a38e190 100644 --- a/frontend/src/Editor/Components/FilePicker.jsx +++ b/frontend/src/Editor/Components/FilePicker.jsx @@ -89,7 +89,7 @@ export const FilePicker = ({ const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, acceptedFiles, fileRejections } = useDropzone({ - accept: parsedFileType, + accept: { parsedFileType: [parsedFileType] }, noClick: !parsedEnablePicker || disablePicker, noDrag: !parsedEnableDropzone || disablePicker, noKeyboard: true, diff --git a/frontend/src/Editor/Components/Html.jsx b/frontend/src/Editor/Components/Html.jsx index d192ec1f5b..fa803e74f3 100644 --- a/frontend/src/Editor/Components/Html.jsx +++ b/frontend/src/Editor/Components/Html.jsx @@ -13,6 +13,13 @@ export const Html = function ({ height, properties, styles, darkMode, dataCy }) useEffect(() => { setRawHtml(stringifyHTML); }, [stringifyHTML]); + DOMPurify.addHook('afterSanitizeAttributes', function (node) { + // set all elements owning target to target=_blank + if ('target' in node) { + node.setAttribute('target', '_blank'); + node.setAttribute('rel', 'noopener'); + } + }); return (
diff --git a/frontend/src/Editor/Components/Icon.jsx b/frontend/src/Editor/Components/Icon.jsx index 68e5bc40ab..19ccbd82b7 100644 --- a/frontend/src/Editor/Components/Icon.jsx +++ b/frontend/src/Editor/Components/Icon.jsx @@ -3,11 +3,13 @@ import React, { useState, useEffect } from 'react'; import * as Icons from '@tabler/icons-react'; import cx from 'classnames'; -export const Icon = ({ properties, styles, fireEvent, width, height, registerAction, darkMode, dataCy }) => { +export const Icon = ({ properties, styles, fireEvent, width, height, registerAction, darkMode, dataCy, component }) => { const { icon } = properties; const { iconColor, visibility } = styles; // eslint-disable-next-line import/namespace const IconElement = Icons[icon]; + const { definition } = component; + const { events = [] } = definition; const color = iconColor === '#000' ? (darkMode ? '#fff' : '#000') : iconColor; @@ -31,7 +33,10 @@ export const Icon = ({ properties, styles, fireEvent, width, height, registerAct }); return ( -
+
0 })} + data-cy={dataCy} + > { event.stopPropagation(); setShowModal(true); - setExposedVariable('show', true).then(() => fireEvent('onOpen')); + setExposedVariable('show', true); }} + data-cy={`${dataCy}-launch-button`} > {triggerButtonLabel ?? 'Show Modal'} @@ -204,11 +205,14 @@ const Component = ({ children, ...restProps }) => { /> )} {!hideTitleBar && ( - - {title} + + + {title} + {!hideCloseButton && ( { e.preventDefault(); @@ -236,7 +240,7 @@ const Component = ({ children, ...restProps }) => { )} )} - + {children} diff --git a/frontend/src/Editor/Components/RangeSlider.jsx b/frontend/src/Editor/Components/RangeSlider.jsx index c19057a24c..cd3dff473f 100644 --- a/frontend/src/Editor/Components/RangeSlider.jsx +++ b/frontend/src/Editor/Components/RangeSlider.jsx @@ -36,7 +36,7 @@ export const RangeSlider = function RangeSlider({ height, properties, styles, se useEffect(() => { setExposedVariable('value', enableTwoHandle ? twoHandlesArray : singleHandleValue); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sliderRef.current, enableTwoHandle]); + }, [enableTwoHandle]); const onSliderChange = (value) => { setExposedVariable('value', value); diff --git a/frontend/src/Editor/Components/Table/AddNewRowComponent.jsx b/frontend/src/Editor/Components/Table/AddNewRowComponent.jsx new file mode 100644 index 0000000000..1f9f662415 --- /dev/null +++ b/frontend/src/Editor/Components/Table/AddNewRowComponent.jsx @@ -0,0 +1,169 @@ +import React, { useEffect, useState } from 'react'; +import { useTable, useBlockLayout } from 'react-table'; +import _ from 'lodash'; +import { Tooltip } from 'react-tooltip'; + +export function AddNewRowComponent({ + hideAddNewRowPopup, + tableType, + darkMode, + mergeToAddNewRowsDetails, + onEvent, + component, + setExposedVariable, + allColumns, + defaultColumn, + columns, + addNewRowsDetails, +}) { + const getNewRowObject = () => { + return allColumns.reduce((accumulator, column) => { + const key = column.key ?? column.exportValue; + accumulator[key] = ''; + return accumulator; + }, {}); + }; + const newRow = getNewRowObject(); + const { newRowsChangeSet } = addNewRowsDetails; + const rowsFromPrevOperationPresent = _.isEmpty(addNewRowsDetails.newRowsDataUpdates) ? false : true; + const previousRowsData = rowsFromPrevOperationPresent + ? Object.keys(addNewRowsDetails.newRowsDataUpdates).reduce((accumulator, row) => { + accumulator[row] = addNewRowsDetails.newRowsDataUpdates[row]; + return accumulator; + }, []) + : null; + const [newRowsState, setNewRowsState] = useState(rowsFromPrevOperationPresent ? previousRowsData : [newRow]); + const newRowData = useTable( + { + columns, + data: newRowsState, + defaultColumn, + }, + useBlockLayout + ); + useEffect(() => { + if (!rowsFromPrevOperationPresent) { + const newRowDataUpdates = newRowsState.reduce((accumulator, row, index) => { + accumulator[index] = newRowsState[index]; + return accumulator; + }, {}); + setExposedVariable('newRows', newRowsState).then(() => { + mergeToAddNewRowsDetails({ newRowsDataUpdates: newRowDataUpdates }); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = newRowData; + + return ( +
+
+
+

+ Add new rows +

+
+
+ +
+
+
+
{translator('header.organization.menus.manageUsers.name', 'Name')}{translator('header.organization.menus.manageUsers.email', 'Email')} + {translator('header.organization.menus.manageUsers.name', 'Name')} + + {translator('header.organization.menus.manageUsers.email', 'Email')} + {translator('header.organization.menus.manageUsers.status', 'Status')} + {translator('header.organization.menus.manageUsers.status', 'Status')} +
+ - + {user.name} - + {user.email} - + {user.status} {user.status === 'invited' && 'invitation_token' in user ? ( - <> +
- + + +

+ Copy link +

+
- +
) : ( '' )}
- +
+ + {headerGroups.map((headerGroup, index) => { + return ( + + {headerGroup.headers.map((column, index) => { + return ( + + ); + })} + + ); + })} + + + {rows.map((row, index) => { + prepareRow(row); + return ( + + {row.cells.map((cell, index) => { + let cellProps = cell.getCellProps(); + const isEditable = true; + return ( + + ); + })} + + ); + })} + +
+
{column.render('Header')}
+
+
+ {cell.render('Cell', { cell, isEditable, newRowsChangeSet })} +
+
+ + +
+
+ + +
+
+ ); +} diff --git a/frontend/src/Editor/Components/Table/CustomSelect.jsx b/frontend/src/Editor/Components/Table/CustomSelect.jsx index 6350a0ad4e..71196b65b9 100644 --- a/frontend/src/Editor/Components/Table/CustomSelect.jsx +++ b/frontend/src/Editor/Components/Table/CustomSelect.jsx @@ -2,7 +2,7 @@ import React from 'react'; import SelectSearch from 'react-select-search'; import { useTranslation } from 'react-i18next'; -export const CustomSelect = ({ options, value, multiple, onChange, darkMode, isEditable, width }) => { +export const CustomSelect = ({ options, value, multiple, onChange, isEditable, width }) => { const { t } = useTranslation(); function renderValue(valueProps) { diff --git a/frontend/src/Editor/Components/Table/Table.jsx b/frontend/src/Editor/Components/Table/Table.jsx index 25d5a9e023..0ebd4f62ea 100644 --- a/frontend/src/Editor/Components/Table/Table.jsx +++ b/frontend/src/Editor/Components/Table/Table.jsx @@ -45,6 +45,7 @@ import GenerateEachCellValue from './GenerateEachCellValue'; // eslint-disable-next-line import/no-unresolved import { toast } from 'react-hot-toast'; import { Tooltip } from 'react-tooltip'; +import { AddNewRowComponent } from './AddNewRowComponent'; export function Table({ id, @@ -125,14 +126,20 @@ export function Table({ const [generatedColumn, setGeneratedColumn] = useState([]); const mergeToTableDetails = (payload) => dispatch(reducerActions.mergeToTableDetails(payload)); const mergeToFilterDetails = (payload) => dispatch(reducerActions.mergeToFilterDetails(payload)); + const mergeToAddNewRowsDetails = (payload) => dispatch(reducerActions.mergeToAddNewRowsDetails(payload)); const mounted = useMounted(); + const prevDataFromProps = useRef(); + useEffect(() => { + if (mounted) prevDataFromProps.current = properties.data; + }, [JSON.stringify(properties.data)]); + useEffect(() => { setExposedVariable( 'filters', tableDetails.filterDetails.filters.map((filter) => filter.value) ); - }, [JSON.stringify(tableDetails.filterDetails.filters)]); + }, [JSON.stringify(tableDetails?.filterDetails?.filters)]); useEffect( () => mergeToTableDetails({ columnProperties: component?.definition?.properties?.columns?.value }), @@ -156,6 +163,14 @@ export function Table({ mergeToFilterDetails({ filtersVisible: false }); } + function showAddNewRowPopup() { + mergeToAddNewRowsDetails({ addingNewRows: true }); + } + + function hideAddNewRowPopup() { + mergeToAddNewRowsDetails({ addingNewRows: false }); + } + const defaultColumn = React.useMemo( () => ({ minWidth: 60, @@ -164,7 +179,7 @@ export function Table({ [] ); - function handleCellValueChange(index, key, value, rowData) { + function handleExistingRowCellValueChange(index, key, value, rowData) { const changeSet = tableDetails.changeSet; const dataUpdates = tableDetails.dataUpdates || []; const clonedTableData = _.cloneDeep(tableData); @@ -198,6 +213,37 @@ export function Table({ return setExposedVariables({ ...changesToBeSavedAndExposed, updatedData: clonedTableData }); } + const copyOfTableDetails = useRef(tableDetails); + useEffect(() => { + copyOfTableDetails.current = _.cloneDeep(tableDetails); + }, [JSON.stringify(tableDetails)]); + + function handleNewRowCellValueChange(index, key, value, rowData) { + const changeSet = copyOfTableDetails.current.addNewRowsDetails.newRowsChangeSet || {}; + const dataUpdates = copyOfTableDetails.current.addNewRowsDetails.newRowsDataUpdates || {}; + let obj = changeSet ? changeSet[index] || {} : {}; + obj = _.set(obj, key, value); + let newChangeset = { + ...changeSet, + [index]: { + ...obj, + }, + }; + obj = _.set({ ...rowData, ...obj }, key, value); + + let newDataUpdates = { + ...dataUpdates, + [index]: { ...obj }, + }; + const changesToBeSaved = { newRowsDataUpdates: newDataUpdates, newRowsChangeSet: newChangeset }; + const changesToBeExposed = Object.keys(newDataUpdates).reduce((accumulator, row) => { + accumulator.push({ ...newDataUpdates[row] }); + return accumulator; + }, []); + mergeToAddNewRowsDetails(changesToBeSaved); + return setExposedVariables({ newRows: changesToBeExposed }); + } + function getExportFileBlob({ columns, fileType, fileName }) { let headers = columns.map((column) => { return { exportValue: String(column.exportValue), key: column.key ? String(column.key) : column.key }; @@ -304,7 +350,7 @@ export function Table({ columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value, columnSizes, currentState, - handleCellValueChange, + handleCellValueChange: handleExistingRowCellValueChange, customFilter, defaultColumn, changeSet: tableDetails.changeSet, @@ -318,6 +364,24 @@ export function Table({ darkMode, }); + const columnDataForAddNewRows = generateColumnsData({ + columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value, + columnSizes, + currentState, + handleCellValueChange: handleNewRowCellValueChange, + customFilter, + defaultColumn, + changeSet: tableDetails.addNewRowsDetails.newRowsChangeSet, + tableData, + variablesExposedForPreview, + exposeToCodeHinter, + id, + fireEvent, + tableRef, + t, + darkMode, + }); + const [leftActionsCellData, rightActionsCellData] = useMemo( () => generateActionsData({ @@ -339,7 +403,9 @@ export function Table({ const optionsData = columnData.map((column) => column.columnOptions?.selectOptions); const columns = useMemo( - () => [...leftActionsCellData, ...columnData, ...rightActionsCellData], + () => { + return [...leftActionsCellData, ...columnData, ...rightActionsCellData]; + }, [ JSON.stringify(columnData), JSON.stringify(tableData), @@ -355,15 +421,29 @@ export function Table({ ] // Hack: need to fix ); - const data = useMemo( - () => tableData, - [ - tableData.length, - tableDetails.changeSet, - component.definition.properties.data.value, - JSON.stringify(properties.data), - ] - ); + const columnsForAddNewRow = useMemo(() => { + return [...columnDataForAddNewRows]; + }, [JSON.stringify(columnDataForAddNewRows), darkMode, tableDetails.addNewRowsDetails.addingNewRows]); + + const data = useMemo(() => { + if (!_.isEqual(properties.data, prevDataFromProps.current)) { + if ( + !_.isEmpty(exposedVariables.newRows) || + !_.isEmpty(tableDetails.addNewRowsDetails.newRowsDataUpdates) || + tableDetails.addNewRowsDetails.addingNewRows + ) { + setExposedVariable('newRows', []).then(() => { + mergeToAddNewRowsDetails({ newRowsDataUpdates: {}, newRowsChangeSet: {}, addingNewRows: false }); + }); + } + } + return tableData; + }, [ + tableData.length, + tableDetails.changeSet, + component.definition.properties.data.value, + JSON.stringify(properties.data), + ]); useEffect(() => { if ( @@ -463,6 +543,7 @@ export function Table({ ]); } ); + const currentColOrder = React.useRef(); const sortOptions = useMemo(() => { @@ -537,6 +618,27 @@ export function Table({ }, [JSON.stringify(tableData), JSON.stringify(tableDetails.changeSet)] ); + registerAction( + 'discardNewlyAddedRows', + async function () { + if ( + tableDetails.addNewRowsDetails.addingNewRows && + (Object.keys(tableDetails.addNewRowsDetails.newRowsChangeSet || {}).length > 0 || + Object.keys(tableDetails.addNewRowsDetails.newRowsDataUpdates || {}).length > 0) + ) { + setExposedVariables({ + newRows: [], + }).then(() => { + mergeToAddNewRowsDetails({ newRowsChangeSet: {}, newRowsDataUpdates: {}, addingNewRows: false }); + }); + } + }, + [ + JSON.stringify(tableDetails.addNewRowsDetails.newRowsChangeSet), + tableDetails.addNewRowsDetails.addingNewRows, + JSON.stringify(tableDetails.addNewRowsDetails.newRowsDataUpdates), + ] + ); useEffect(() => { const selectedRowsOriginalData = selectedFlatRows.map((row) => row.original); @@ -656,7 +758,7 @@ export function Table({ > {/* Show top bar unless search box is disabled and server pagination is enabled */} {(displaySearchBox || showDownloadButton || showFilterButton) && ( -
+
)}
+ + {showFilterButton && ( <> @@ -1047,6 +1167,21 @@ export function Table({ fireEvent={fireEvent} /> )} + {tableDetails.addNewRowsDetails.addingNewRows && ( + + )} ); } diff --git a/frontend/src/Editor/Components/Table/columns/index.jsx b/frontend/src/Editor/Components/Table/columns/index.jsx index 9b0f1c7f10..d59f313e88 100644 --- a/frontend/src/Editor/Components/Table/columns/index.jsx +++ b/frontend/src/Editor/Components/Table/columns/index.jsx @@ -55,13 +55,14 @@ export default function generateColumnsData({ column.dateFormat = column.dateFormat ? column.dateFormat : 'DD/MM/YYYY'; column.parseDateFormat = column.parseDateFormat ?? column.dateFormat; //backwards compatibility sortType = (firstDate, secondDate) => { + const columnKey = column.key || column.name; // Return -1 if second date is higher, 1 if first date is higher - if (secondDate?.original[column.name] === '') { + if (secondDate?.original[columnKey] === '') { return 1; - } else if (firstDate?.original[column.name] === '') return -1; + } else if (firstDate?.original[columnKey] === '') return -1; - const parsedFirstDate = moment(firstDate?.original[column.name], column.parseDateFormat); - const parsedSecondDate = moment(secondDate?.original[column.name], column.parseDateFormat); + const parsedFirstDate = moment(firstDate?.original[columnKey], column.parseDateFormat); + const parsedSecondDate = moment(secondDate?.original[columnKey], column.parseDateFormat); if (moment(parsedSecondDate).isSameOrAfter(parsedFirstDate)) { return -1; @@ -91,8 +92,9 @@ export default function generateColumnsData({ regex: column.regex, customRule: column?.customRule, sortType, - Cell: function ({ cell, isEditable }) { - const rowChangeSet = changeSet ? changeSet[cell.row.index] : null; + Cell: function ({ cell, isEditable, newRowsChangeSet = null }) { + const updatedChangeSet = newRowsChangeSet === null ? changeSet : newRowsChangeSet; + const rowChangeSet = updatedChangeSet ? updatedChangeSet[cell.row.index] : null; let cellValue = rowChangeSet ? rowChangeSet[column.key || column.name] ?? cell.value : cell.value; const rowData = tableData[cell.row.index]; @@ -147,7 +149,7 @@ export default function generateColumnsData({
{ if (e.key === 'Enter') { if (e.target.defaultValue !== e.target.value) { @@ -214,7 +216,7 @@ export default function generateColumnsData({
{ if (e.key === 'Enter') { if (e.target.defaultValue !== e.target.value) { @@ -258,7 +260,7 @@ export default function generateColumnsData({ darkMode ? 'text-light textarea-dark-theme' : 'text-muted' }`} readOnly={!isEditable} - style={{ maxWidth: width, minWidth: width - 10 }} + style={{ maxWidth: width }} onBlur={(e) => { if (isEditable) { handleCellValueChange(cell.row.index, column.key || column.name, e.target.value, cell.row.original); diff --git a/frontend/src/Editor/Components/Table/reducer.js b/frontend/src/Editor/Components/Table/reducer.js index 4630fe25c0..479d737674 100644 --- a/frontend/src/Editor/Components/Table/reducer.js +++ b/frontend/src/Editor/Components/Table/reducer.js @@ -5,6 +5,11 @@ export const initialState = () => ({ filters: [], filtersVisible: false, }, + addNewRowsDetails: { + addingNewRows: false, + newRowsDataUpdates: {}, + newRowsChangeSet: {}, + }, }); const mergeToTableDetails = (payload) => ({ @@ -17,9 +22,15 @@ const mergeToFilterDetails = (payload) => ({ payload, }); +const mergeToAddNewRowsDetails = (payload) => ({ + type: 'MERGE_TO_ADD_NEW_ROWS', + payload, +}); + export const reducerActions = { mergeToFilterDetails, mergeToTableDetails, + mergeToAddNewRowsDetails, }; export const reducer = (state, action) => { @@ -38,7 +49,14 @@ export const reducer = (state, action) => { ...action.payload, }, }; - + case 'MERGE_TO_ADD_NEW_ROWS': + return { + ...state, + addNewRowsDetails: { + ...state.addNewRowsDetails, + ...action.payload, + }, + }; default: return initialState(); } diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index eedb5617f0..b2c0041d8f 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -567,7 +567,7 @@ export const Container = ({ guide {' '} - on adding widgets. + on adding components.
)} diff --git a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx index f8321ee292..f251900ef0 100644 --- a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx +++ b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx @@ -19,6 +19,8 @@ import { capitalize, isEmpty } from 'lodash'; import { Card } from '@/_ui/Card'; import { withTranslation, useTranslation } from 'react-i18next'; import { camelizeKeys, decamelizeKeys } from 'humps'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; class DataSourceManagerComponent extends React.Component { constructor(props) { @@ -75,13 +77,13 @@ class DataSourceManagerComponent extends React.Component { componentDidUpdate(prevProps) { if (prevProps.selectedDataSource !== this.props.selectedDataSource) { let dataSourceMeta = this.getDataSourceMeta(this.props.selectedDataSource); - this.setState({ selectedDataSource: this.props.selectedDataSource, options: this.props.selectedDataSource?.options, dataSourceMeta, dataSourceSchema: this.props.selectedDataSource?.plugin?.manifestFile?.data, selectedDataSourceIcon: this.props.selectedDataSource?.plugin?.iconFile?.data, + connectionTestError: null, }); } } @@ -183,6 +185,7 @@ class DataSourceManagerComponent extends React.Component { environment_id: currentEnvironment, }) .then(() => { + this.props.updateSelectedDatasource(selectedDataSource.name); this.setState({ isSaving: false }); this.hideModal(); toast.success( @@ -211,11 +214,14 @@ class DataSourceManagerComponent extends React.Component { }) .then((data) => { this.setState({ isSaving: false }); + this.props.updateSelectedDatasource(name); + this.hideModal(); toast.success( this.props.t('editor.queryManager.dataSourceManager.toast.success.dataSourceAdded', 'Datasource Added'), { position: 'top-center' } ); + this.props.dataSourcesChanged(false, data); this.props.globalDataSourcesChanged && this.props.globalDataSourcesChanged(); }) @@ -350,6 +356,7 @@ class DataSourceManagerComponent extends React.Component { queryString={this.state.queryString} activeDatasourceList={this.state.activeDatasourceList} scope={this.state.scope} + className="tj-text" />
{datasources.map((datasource) => ( @@ -398,7 +405,12 @@ class DataSourceManagerComponent extends React.Component { { type: 'All Datasources', key: '#alldatasources', - list: [...allDataSourcesList.databases, ...allDataSourcesList.apis, ...allDataSourcesList.cloudStorages], + list: [ + ...allDataSourcesList.databases, + ...allDataSourcesList.apis, + ...allDataSourcesList.cloudStorages, + ...allDataSourcesList.plugins, + ], renderDatasources: () => this.renderCardGroup(allDataSourcesList, 'All Datasources'), }, { @@ -475,7 +487,7 @@ class DataSourceManagerComponent extends React.Component { if (this.state.queryString && this.state.queryString.length > 0) { const filteredDatasources = this.state.filteredDatasources.map((datasource) => { - const src = datasource.iconFile?.data + const src = datasource?.iconFile?.data ? `data:image/svg+xml;base64,${datasource.iconFile?.data}` : datasource.kind.toLowerCase(); @@ -488,7 +500,7 @@ class DataSourceManagerComponent extends React.Component { return ( <> -
+

{type}

{filteredDatasources.map((item) => ( renderSelectedDatasource(item)} - usePluginIcon={isEmpty(item.iconFile?.data)} + usePluginIcon={isEmpty(item?.iconFile?.data)} height="35px" width="35px" /> @@ -584,7 +596,7 @@ class DataSourceManagerComponent extends React.Component { } const datasources = source.map((datasource) => { - const src = datasource.iconFile?.data + const src = datasource?.iconFile?.data ? `data:image/svg+xml;base64,${datasource.iconFile?.data}` : datasource.kind.toLowerCase(); @@ -605,7 +617,7 @@ class DataSourceManagerComponent extends React.Component { title={item.title} src={item?.src} handleClick={() => renderSelectedDatasource(item)} - usePluginIcon={isEmpty(item.iconFile?.data)} + usePluginIcon={isEmpty(item?.iconFile?.data)} height="35px" width="35px" /> @@ -654,7 +666,7 @@ class DataSourceManagerComponent extends React.Component { size={selectedDataSource ? 'lg' : 'xl'} onEscapeKeyDown={this.hideModal} className={selectedDataSource ? 'animation-fade' : 'select-datasource-list-modal animation-fade'} - contentClassName={this.props.darkMode ? 'theme-dark' : ''} + contentClassName={`${this.props.darkMode ? 'dark-theme' : ''}`} animation={false} onExit={this.onExit} container={this.props.container} @@ -690,25 +702,29 @@ class DataSourceManagerComponent extends React.Component { data-cy="data-source-name-input-filed" autoFocus /> - - - + {!this.props.isEditing && ( + + + + )}
)} {!selectedDataSource && ( - + {this.props.t('editor.queryManager.dataSourceManager.addNewDataSource', 'Add new datasource')} )} - this.hideModal()} - > - - + {!this.props.isEditing && ( + this.hideModal()} + > + + + )} {this.renderEnvironmentsTab(selectedDataSource)} @@ -722,22 +738,10 @@ class DataSourceManagerComponent extends React.Component {
- - - +
-

+

{this.props.t( 'editor.queryManager.dataSourceManager.whiteListIP', 'Please white-list our IP address if the data source is not publicly accessible.' @@ -758,25 +762,16 @@ class DataSourceManagerComponent extends React.Component { this.setState({ isCopied: true }); }} > - + )}

@@ -795,17 +790,16 @@ class DataSourceManagerComponent extends React.Component { )}
- +
)} @@ -832,22 +829,29 @@ class DataSourceManagerComponent extends React.Component { {!dataSourceMeta?.hideSave && selectedDataSource && dataSourceMeta.customTesting && (
- +
)} diff --git a/frontend/src/Editor/DataSourceManager/TestConnection.jsx b/frontend/src/Editor/DataSourceManager/TestConnection.jsx index 811e5a394c..2493550f44 100644 --- a/frontend/src/Editor/DataSourceManager/TestConnection.jsx +++ b/frontend/src/Editor/DataSourceManager/TestConnection.jsx @@ -2,8 +2,9 @@ import React, { useEffect, useState } from 'react'; import { toast } from 'react-hot-toast'; import { datasourceService } from '@/_services'; import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; -export const TestConnection = ({ kind, options, pluginId, onConnectionTestFailed, darkMode }) => { +export const TestConnection = ({ kind, options, pluginId, onConnectionTestFailed }) => { const [isTesting, setTestingStatus] = useState(false); const [connectionStatus, setConnectionStatus] = useState('unknown'); const [buttonText, setButtonText] = useState('Test Connection'); @@ -59,14 +60,15 @@ export const TestConnection = ({ kind, options, pluginId, onConnectionTestFailed )} {connectionStatus === 'unknown' && ( - + )}
); diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index f5f58b02b8..2e3f07cd80 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -176,7 +176,6 @@ export const DraggableBox = function DraggableBox({ }; const layoutData = inCanvas ? layouts[currentLayout] || defaultData : defaultData; - const gridWidth = canvasWidth / 43; const width = (canvasWidth * layoutData.width) / 43; diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index b55eaf0fba..d2a4d61d22 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -165,6 +165,9 @@ class EditorComponent extends React.Component { document.title = name ? `${name} - Tooljet` : `Untitled App - Tooljet`; } + onVersionDelete = () => { + this.fetchApp(this.props.params.pageHandle); + }; getCurrentOrganizationDetails() { const currentSession = authenticationService.currentSessionValue; const currentUser = currentSession?.current_user; @@ -978,6 +981,7 @@ class EditorComponent extends React.Component { return this.setState({ draftQuery: { ...this.state.draftQuery, name: newName }, renameQueryName: false, + selectedQuery: { ...this.state.selectedQuery, name: newName }, }); } dataqueryService @@ -1402,12 +1406,13 @@ class EditorComponent extends React.Component { ); }; - deletePageRequest = (pageId, isHomePage = false) => { + deletePageRequest = (pageId, isHomePage = false, pageName = '') => { this.setState({ showPageDeletionConfirmation: { isOpen: true, pageId, isHomePage, + pageName, }, }); }; @@ -1418,6 +1423,7 @@ class EditorComponent extends React.Component { isOpen: false, pageId: null, isHomePage: false, + pageName: null, }, }); }; @@ -1836,7 +1842,7 @@ class EditorComponent extends React.Component { this.executeDeletepageRequest()} onCancel={() => this.cancelDeletePageRequest()} @@ -1870,6 +1876,7 @@ class EditorComponent extends React.Component { handleSlugChange={this.handleSlugChange} onVersionRelease={this.onVersionRelease} saveEditingVersion={this.saveEditingVersion} + onVersionDelete={this.onVersionDelete} currentUser={this.state.currentUser} /> diff --git a/frontend/src/Editor/Header/index.js b/frontend/src/Editor/Header/index.js index 4f29f96e79..3b1a17b93d 100644 --- a/frontend/src/Editor/Header/index.js +++ b/frontend/src/Editor/Header/index.js @@ -40,6 +40,7 @@ export default function EditorHeader({ handleSlugChange, onVersionRelease, saveEditingVersion, + onVersionDelete, currentUser, }) { const { is_maintenance_on } = app; @@ -124,6 +125,7 @@ export default function EditorHeader({ setAppDefinitionFromVersion={setAppDefinitionFromVersion} showCreateVersionModalPrompt={showCreateVersionModalPrompt} closeCreateVersionModalPrompt={closeCreateVersionModalPrompt} + onVersionDelete={onVersionDelete} /> )}
diff --git a/frontend/src/Editor/Inspector/EventManager.jsx b/frontend/src/Editor/Inspector/EventManager.jsx index f9ab1f50f8..795f2cee25 100644 --- a/frontend/src/Editor/Inspector/EventManager.jsx +++ b/frontend/src/Editor/Inspector/EventManager.jsx @@ -693,7 +693,7 @@ export const EventManager = ({ )}
{t('editor.inspector.eventManager.debounce', 'Debounce')}
-
+
{ +const LeftSidebarDataSourcesContainer = ({ darkMode, RenderDataSource, dataSources = [] }) => { const { t } = useTranslation(); return (
diff --git a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx index 3c079e69d7..dcc88e8a05 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx @@ -3,13 +3,17 @@ import { LeftSidebarItem } from './SidebarItem'; import { Button, HeaderSection } from '@/_ui/LeftSidebar'; import JSONTreeViewer from '@/_ui/JSONTreeViewer'; import _ from 'lodash'; -import RunjsIcon from '../Icons/runjs.svg'; -import RunTooljetDbIcon from '../Icons/tooljetdb.svg'; -import RunpyIcon from '../Icons/runpy.svg'; import { toast } from 'react-hot-toast'; import { getSvgIcon } from '@/_helpers/appUtils'; import Popover from '@/_ui/Popover'; +const staticDataSources = [ + { kind: 'tooljetdb', id: 'null', name: 'Tooljet Database' }, + { kind: 'restapi', id: 'null', name: 'REST API' }, + { kind: 'runjs', id: 'runjs', name: 'Run JavaScript code' }, + { kind: 'runpy', id: 'runpy', name: 'Run Python code' }, +]; + export const LeftSidebarInspector = ({ darkMode, currentState, @@ -77,19 +81,11 @@ export const LeftSidebarInspector = ({ }, [currentState]); const queryIcons = Object.entries(currentState['queries']).map(([key, value]) => { - if (value.kind === 'runjs') { - return { iconName: key, jsx: () => }; - } - if (value.kind === 'tooljetdb') { - return { iconName: key, jsx: () => }; - } + const allDs = [...staticDataSources, ...dataSources]; - if (value.kind === 'runpy') { - return { iconName: key, jsx: () => }; - } - const icon = dataSources.find((ds) => ds.kind === value.kind); - const iconFile = icon?.plugin?.icon_file?.data ?? undefined; - const Icon = () => getSvgIcon(icon?.kind, 25, 25, iconFile ?? undefined); + const icon = allDs.find((ds) => ds.kind === value.kind); + const iconFile = icon?.plugin?.iconFile?.data ?? undefined; + const Icon = () => getSvgIcon(icon?.kind, 16, 16, iconFile ?? undefined); return { iconName: key, jsx: () => }; }); diff --git a/frontend/src/Editor/LeftSidebar/SidebarPageSelector/PageHandler.jsx b/frontend/src/Editor/LeftSidebar/SidebarPageSelector/PageHandler.jsx index 32a132abcf..e61f8d0215 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarPageSelector/PageHandler.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarPageSelector/PageHandler.jsx @@ -54,7 +54,7 @@ export const PageHandler = ({ setIsHovered(false); switch (id) { case 'delete-page': - deletePage(page.id, isHomePage); + deletePage(page.id, isHomePage, page.name); break; case 'rename-page': diff --git a/frontend/src/Editor/LeftSidebar/SidebarPageSelector/SettingsModal.jsx b/frontend/src/Editor/LeftSidebar/SidebarPageSelector/SettingsModal.jsx index 2199f85864..285404a56d 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarPageSelector/SettingsModal.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarPageSelector/SettingsModal.jsx @@ -65,7 +65,7 @@ export const SettingsModal = ({ }, }, }} - componentMeta={{ events: { onPageLoad: { displayName: 'On page load' } } }} + componentMeta={{ events: { onPageLoad: { displayName: 'On page load' } }, name: 'page' }} currentState={currentState} dataQueries={dataQueries} components={components} diff --git a/frontend/src/Editor/LeftSidebar/index.jsx b/frontend/src/Editor/LeftSidebar/index.jsx index 887d325bcd..24791c6c0c 100644 --- a/frontend/src/Editor/LeftSidebar/index.jsx +++ b/frontend/src/Editor/LeftSidebar/index.jsx @@ -109,7 +109,7 @@ export const LeftSidebar = forwardRef((props, ref) => { setSelectedComponent={setSelectedComponent} removeComponent={removeComponent} runQuery={runQuery} - dataSources={dataSources} + dataSources={globalDataSources} popoverContentHeight={popoverContentHeight} /> {dataSources?.length > 0 && ( diff --git a/frontend/src/Editor/QueryManager/QueryEditors/GRPC.jsx b/frontend/src/Editor/QueryManager/QueryEditors/GRPC.jsx new file mode 100644 index 0000000000..d0ee29ae57 --- /dev/null +++ b/frontend/src/Editor/QueryManager/QueryEditors/GRPC.jsx @@ -0,0 +1,348 @@ +import React from 'react'; +import Select from '@/_ui/Select'; +import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles'; +import { Tab, ListGroup, Row } from 'react-bootstrap'; +import { CodeHinter } from '../../CodeBuilder/CodeHinter'; + +const GRPCComponent = ({ darkMode, selectedDataSource, ...restProps }) => { + const protobufDefintion = JSON.parse(selectedDataSource?.options?.protobuf?.value); + + const { options, optionsChanged, queryName } = restProps; + + const serverUrl = selectedDataSource?.options?.url?.value; + + const [serviceNames, setServiceNames] = React.useState([]); + const [selectedServiceName, setSelectedServiceName] = React.useState({ + label: options?.serviceName ?? '', + value: options?.serviceName ?? '', + }); + const [rpc, setRpc] = React.useState(options.rpc ?? ''); + const [metaDataOptions, setMetaDataOptions] = React.useState(() => { + if (options?.metaDataOptions) { + return JSON.parse(options?.metaDataOptions); + } + return []; + }); + + React.useEffect(() => { + if (protobufDefintion) { + const serviceNamesFromDef = Object.keys(protobufDefintion).map((serviceName) => ({ + label: serviceName, + value: serviceName, + rpcs: protobufDefintion[serviceName]?.map((rpc) => ({ label: rpc, value: rpc })), + })); + setServiceNames(serviceNamesFromDef); + + setSelectedServiceName(serviceNamesFromDef[0]); + + optionsChanged({ + ...options, + serviceName: serviceNamesFromDef[0].value, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const addNewMetaDataKeyValuePair = () => { + const currentMetaDataOptions = JSON.parse(JSON.stringify(metaDataOptions)); + currentMetaDataOptions.push({ key: '', value: '' }); + setMetaDataOptions(currentMetaDataOptions); + }; + + const removeMetaDataKeyValuePair = (index) => { + const currentMetaDataOptions = JSON.parse(JSON.stringify(metaDataOptions)); + currentMetaDataOptions.splice(index, 1); + setMetaDataOptions(currentMetaDataOptions); + }; + + const handleOnMetaDataKeyChange = (type, index, value) => { + const currentMetaDataOptions = JSON.parse(JSON.stringify(metaDataOptions)); + currentMetaDataOptions[index][type === 'key' ? 0 : 1] = value; + + setMetaDataOptions(currentMetaDataOptions); + optionsChanged({ + ...options, + metaDataOptions: JSON.stringify(currentMetaDataOptions), + }); + }; + + const handleJsonBodyChanged = (value) => { + optionsChanged({ + ...options, + jsonMessage: value, + }); + }; + + const customSelectStyles = (darkMode, width) => { + return { + ...queryManagerSelectComponentStyle(darkMode, width), + control: (provided) => ({ + ...provided, + boxShadow: 'none', + backgroundColor: darkMode ? '#2b3547' : '#F1F3F5', + borderRadius: '0px', + borderLeft: 'none', + height: 32, + minHeight: 32, + borderColor: darkMode ? 'inherit' : ' #D7DBDF', + borderWidth: '1px', + '&:hover': { + backgroundColor: darkMode ? '' : '#F8F9FA', + }, + '&:active': { + backgroundColor: darkMode ? '' : '#F8FAFF', + borderColor: '#3E63DD', + boxShadow: '0px 0px 0px 2px #C6D4F9 ', + }, + cursor: 'pointer', + }), + }; + }; + + return ( +
+
+
+ +
+
+ { + setRpc(value); + optionsChanged({ + ...options, + rpc: value, + }); + }} + value={rpc} + placeholder="Select a RPC" + height={32} + styles={customSelectStyles(darkMode, 170)} + useCustomStyles={true} + /> +
+
+
+
+ +
+ +
+
+ ); +}; + +function ControlledTabs({ + metaDataoptions, + currentState, + theme, + onChange, + onJsonBodyChange, + removeKeyValuePair, + addNewKeyValuePair, + darkMode, + componentName, + messageJSON, +}) { + const [key, setKey] = React.useState('message'); + const tabs = ['Message', 'Metadata']; + + return ( + setKey(k)} defaultActiveKey="message"> + +
+ + {tabs.map((tab) => ( + + {tab} + + ))} + +
+ +
+ + + + + + +
+
+ onJsonBodyChange(value)} + componentName={`${componentName}/message`} + enablePreview={false} + /> +
+
+
+
+
+
+
+ ); +} + +const TabContent = ({ + options = [], + currentState, + theme, + onChange, + componentName, + removeKeyValuePair, + paramType, + tabType, + addNewKeyValuePair, + darkMode, +}) => { + return ( +
+ {options?.map((option, index) => { + return ( +
+
+
{index + 1}
+
+ onChange('key', index, value)} + componentName={`${componentName}/${tabType}::key::${index}`} + /> +
+
+ onChange('value', index, value)} + componentName={`${componentName}/${tabType}::value::${index}`} + /> +
+
{ + removeKeyValuePair(paramType, index); + }} + > + + + + + +
+
+
+ ); + })} + +
+
addNewKeyValuePair(paramType)} + role="button" + > + + + + + +
+
+
+
+ ); +}; + +export const BaseUrl = ({ dataSourceURL, theme }) => { + return ( + + {dataSourceURL} + + ); +}; + +GRPCComponent.ServerUrl = BaseUrl; +GRPCComponent.Tabs = ControlledTabs; +GRPCComponent.TabContent = TabContent; + +export default GRPCComponent; diff --git a/frontend/src/Editor/QueryManager/QueryEditors/Openapi.jsx b/frontend/src/Editor/QueryManager/QueryEditors/Openapi.jsx index 9859a2f888..39a05487f7 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/Openapi.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/Openapi.jsx @@ -149,7 +149,11 @@ class OpenapiComponent extends React.Component { return servers.map((url) => { return url.url; }); - } else if (path && this.state.spec.paths[path]['servers'] && this.state.spec?.paths[path]['servers'].length > 0) { + } else if ( + path && + this.state.spec.paths[path]?.['servers'] && + this.state.spec.paths[path]?.['servers'].length > 0 + ) { const servers = this.state.spec.paths[path]['servers']; return servers.map((url) => { return url.url; @@ -167,7 +171,7 @@ class OpenapiComponent extends React.Component { const path = this.state.options.path; if (operation.parameters) { - if (this.state.spec.paths[path]['parameters']) { + if (this.state.spec.paths[path]?.['parameters']) { const generalParams = this.state.spec.paths[path]['parameters'].filter((param) => param.in === paramType); const operationParams = operation.parameters.filter((param) => param.in === paramType); const result = generalParams.concat(operationParams).filter(function (o) { @@ -176,7 +180,7 @@ class OpenapiComponent extends React.Component { return result; } return operation.parameters.filter((param) => param.in === paramType); - } else if (this.state.spec.paths[path]['parameters']) + } else if (this.state.spec.paths[path]?.['parameters']) return this.state.spec.paths[path]['parameters'].filter((param) => param.in === paramType); else return []; } diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx index b8ba931291..c33a49792a 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/CreateRow.jsx @@ -88,6 +88,7 @@ export const CreateRow = React.memo(({ currentState, optionchanged, options, dar value={column} options={displayColumns} onChange={handleColumnChange} + customWrap={true} />
diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx index 92d219d748..6078539ec0 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DeleteRows.jsx @@ -44,18 +44,11 @@ export const DeleteRows = React.memo(({ currentState, darkMode }) => { } const RenderFilterFields = ({ column, operator, value, id }) => { - const existingColumnOptions = Object.values(deleteRowsOptions?.where_filters).map((f) => f.column); let displayColumns = columns.map(({ accessor }) => ({ value: accessor, label: accessor, })); - if (existingColumnOptions.length > 0) { - displayColumns = displayColumns.filter( - ({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value) - ); - } - const handleColumnChange = (selectedOption) => { updateFilterOptionsChanged({ ...deleteRowsOptions?.where_filters[id], ...{ column: selectedOption } }); }; diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx index 5baac5e090..bdf8aa51aa 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/ListRows.jsx @@ -77,19 +77,11 @@ export const ListRows = React.memo(({ currentState, darkMode }) => { } const RenderFilterFields = ({ column, operator, value, id }) => { - const existingColumnOptions = Object.values(listRowsOptions?.where_filters).map((f) => f.column); - let displayColumns = columns.map(({ accessor }) => ({ value: accessor, label: accessor, })); - if (existingColumnOptions.length > 0) { - displayColumns = displayColumns.filter( - ({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value) - ); - } - const handleColumnChange = (selectedOption) => { updateFilterOptionsChanged({ ...listRowsOptions?.where_filters[id], ...{ column: selectedOption } }); }; diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx index 3073af0c45..dccbae1e4e 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/UpdateRows.jsx @@ -70,18 +70,11 @@ export const UpdateRows = React.memo(({ currentState, darkMode }) => { } const RenderFilterFields = ({ column, operator, value, id }) => { - const existingColumnOptions = Object.values(updateRowsOptions?.where_filters).map((f) => f.column); let displayColumns = columns.map(({ accessor }) => ({ value: accessor, label: accessor, })); - if (existingColumnOptions.length > 0) { - displayColumns = displayColumns.filter( - ({ value }) => !existingColumnOptions.map((item) => item !== column && item).includes(value) - ); - } - const handleColumnChange = (selectedOption) => { updateFilterOptionsChanged({ ...updateRowsOptions?.where_filters[id], ...{ column: selectedOption } }); }; diff --git a/frontend/src/Editor/QueryManager/QueryEditors/index.js b/frontend/src/Editor/QueryManager/QueryEditors/index.js index 9ca994c2dd..d08b6481c3 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/index.js +++ b/frontend/src/Editor/QueryManager/QueryEditors/index.js @@ -8,6 +8,7 @@ import { Runjs } from './Runjs'; import { Runpy } from './Runpy'; import { Stripe } from './Stripe'; import { Openapi } from './Openapi'; +import Grpc from './GRPC'; import tooljetDbOperations from './TooljetDatabase/operations.json'; import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles'; @@ -32,6 +33,7 @@ export const allSources = { Runpy, Stripe, Openapi, + Grpc, }; export const source = (props) => ( diff --git a/frontend/src/Editor/QueryManager/QueryManager.jsx b/frontend/src/Editor/QueryManager/QueryManager.jsx index 442845d23e..bf39b5ab60 100644 --- a/frontend/src/Editor/QueryManager/QueryManager.jsx +++ b/frontend/src/Editor/QueryManager/QueryManager.jsx @@ -167,6 +167,14 @@ class QueryManagerComponent extends React.Component { ); }; + componentDidUpdate(prevState) { + if (prevState?.selectedQuery?.name !== this.state?.selectedQuery?.name) { + this.setState({ + queryName: this.state.selectedQuery?.name, + }); + } + } + componentWillReceiveProps(nextProps) { if (nextProps.loadingDataSources) return; if (this.props.showQueryConfirmation && !nextProps.showQueryConfirmation) { @@ -230,7 +238,7 @@ class QueryManagerComponent extends React.Component { }; changeDataSource = (source) => { - const isSchemaUnavailable = ['restapi', 'stripe', 'runjs', 'runpy', 'tooljetdb'].includes(source.kind); + const isSchemaUnavailable = ['restapi', 'stripe', 'runjs', 'runpy', 'tooljetdb', 'grpc'].includes(source.kind); const schemaUnavailableOptions = { restapi: { method: 'get', @@ -249,6 +257,7 @@ class QueryManagerComponent extends React.Component { code: '', }, runpy: {}, + grpc: {}, }; let newOptions = {}; diff --git a/frontend/src/Editor/SubContainer.jsx b/frontend/src/Editor/SubContainer.jsx index 67af3a56bb..337cb9ede1 100644 --- a/frontend/src/Editor/SubContainer.jsx +++ b/frontend/src/Editor/SubContainer.jsx @@ -71,7 +71,8 @@ export const SubContainer = ({ const allComponents = appDefinition ? appDefinition.pages[currentPageId].components : {}; const isParentModal = (allComponents[parent]?.component?.component === 'Modal' || - allComponents[parent]?.component?.component === 'Form') ?? + allComponents[parent]?.component?.component === 'Form' || + allComponents[parent]?.component?.component === 'Container') ?? false; const getChildWidgets = (components) => { @@ -462,7 +463,7 @@ export const SubContainer = ({ ref={drop} style={styles} id={`canvas-${parent}`} - className={`real-canvas ${(isDragging || isResizing) && !readOnly ? ' show-grid' : ''}`} + className={`real-canvas ${(isDragging || isResizing) && !readOnly ? 'show-grid' : ''}`} > {checkParentVisibility() && Object.keys(childWidgets).map((key) => { diff --git a/frontend/src/Editor/Viewer/ViewerNavigation.jsx b/frontend/src/Editor/Viewer/ViewerNavigation.jsx index 8f1b76241f..b8f06f8f60 100644 --- a/frontend/src/Editor/Viewer/ViewerNavigation.jsx +++ b/frontend/src/Editor/Viewer/ViewerNavigation.jsx @@ -6,6 +6,7 @@ import LogoIcon from '@assets/images/rocket.svg'; import { Link } from 'react-router-dom'; import { DarkModeToggle } from '@/_components/DarkModeToggle'; import Header from './Header'; +import FolderList from '@/_ui/FolderList/FolderList'; export const ViewerNavigation = ({ isMobileDevice, pages, currentPageId, switchPage, darkMode }) => { if (isMobileDevice) { @@ -24,17 +25,11 @@ export const ViewerNavigation = ({ isMobileDevice, pages, currentPageId, switchP {pages.map( ([id, page]) => !page.hidden && ( -
switchPage(id)} - className={`viewer-page-handler cursor-pointer ${darkMode && 'dark'}`} - > -
-
- {_.truncate(page.name, { length: 18 })} -
-
-
+ switchPage(id)} selectedItem={id === currentPageId}> + + {_.truncate(page.name, { length: 18 })} + + ) )}
diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index 55c17a6990..9a1d5e9120 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -285,7 +285,7 @@ export const widgets = [ }, defaultSize: { width: 20, - height: 300, + height: 358, }, events: { onRowHovered: { displayName: 'Row hovered' }, @@ -297,6 +297,7 @@ export const widgets = [ onSort: { displayName: 'Sort applied' }, onCellValueChanged: { displayName: 'Cell value changed' }, onFilterChanged: { displayName: 'Filter changed' }, + onNewRowsAdded: { displayName: 'Add new rows' }, }, styles: { textColor: { @@ -397,6 +398,10 @@ export const widgets = [ handle: 'discardChanges', displayName: 'Discard Changes', }, + { + handle: 'discardNewlyAddedRows', + displayName: 'Discard newly added rows', + }, ], definition: { others: { diff --git a/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx b/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx index 83b8bb9fec..995d35344e 100644 --- a/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx +++ b/frontend/src/GlobalDatasources/CreateDataSourceModal/index.jsx @@ -2,12 +2,12 @@ import React, { useContext } from 'react'; import { GlobalDataSourcesContext } from '../index'; import { List } from '../List'; -export const CreateDataSourceModal = () => { +export const CreateDataSourceModal = ({ updateSelectedDatasource }) => { const { handleModalVisibility, setEditing } = useContext(GlobalDataSourcesContext); return (
-
+
- +
); }; diff --git a/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx b/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx index f867931e76..9d1376fe3e 100644 --- a/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx +++ b/frontend/src/GlobalDatasources/GlobalDataSourcesPage/index.jsx @@ -5,11 +5,11 @@ import { GlobalDataSourcesContext } from '..'; import { DataSourceManager } from '../../Editor/DataSourceManager'; import DataSourceFolder from '@assets/images/icons/datasource-folder.svg'; -export const GlobalDataSourcesPage = ({ darkMode }) => { +export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasource }) => { const containerRef = useRef(null); const [modalProps, setModalProps] = useState({ backdrop: false, - dialogClassName: 'datasource-edit-modal', + dialogClassName: `datasource-edit-modal`, enforceFocus: false, }); @@ -36,6 +36,7 @@ export const GlobalDataSourcesPage = ({ darkMode }) => { if (!isEditing) { setModalProps({ ...modalProps, backdrop: true }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDataSource, isEditing]); const handleHideModal = () => { @@ -43,6 +44,7 @@ export const GlobalDataSourcesPage = ({ darkMode }) => { if (!isEditing) { setEditing(true); setSelectedDataSource(dataSources[0]); + updateSelectedDatasource(dataSources[0]?.name); } else { setSelectedDataSource(null); setEditing(true); @@ -50,10 +52,11 @@ export const GlobalDataSourcesPage = ({ darkMode }) => { } } else { handleModalVisibility(); + setEditing(true); } }; - const environmentChanged = (env, dataSourceId) => { + const environmentChanged = (env) => { setCurrentEnvironment(env); }; @@ -64,13 +67,8 @@ export const GlobalDataSourcesPage = ({ darkMode }) => { return (
- -
+ +
{containerRef && containerRef?.current && ( { environments={environments} environmentChanged={environmentChanged} container={selectedDataSource ? containerRef?.current : null} + isEditing={isEditing} + updateSelectedDatasource={updateSelectedDatasource} /> )} {!selectedDataSource && isEditing && ( diff --git a/frontend/src/GlobalDatasources/LIstItem/index.jsx b/frontend/src/GlobalDatasources/LIstItem/index.jsx index 3db7aee4b3..8b247fd5d3 100644 --- a/frontend/src/GlobalDatasources/LIstItem/index.jsx +++ b/frontend/src/GlobalDatasources/LIstItem/index.jsx @@ -5,7 +5,7 @@ import { DataSourceTypes } from '../../Editor/DataSourceManager/SourceComponents import { getSvgIcon } from '@/_helpers/appUtils'; import DeleteIcon from '../Icons/DeleteIcon.svg'; -export const ListItem = ({ dataSource, key, active, onDelete }) => { +export const ListItem = ({ dataSource, key, active, onDelete, updateSelectedDatasource }) => { const { setSelectedDataSource, toggleDataSourceManagerModal, environments, setCurrentEnvironment } = useContext(GlobalDataSourcesContext); @@ -28,7 +28,7 @@ export const ListItem = ({ dataSource, key, active, onDelete }) => { return (
@@ -39,16 +39,17 @@ export const ListItem = ({ dataSource, key, active, onDelete }) => { setCurrentEnvironment(environments[0]); toggleDataSourceManagerModal(true); focusModal(); + updateSelectedDatasource(dataSource?.name); }} className="col d-flex align-items-center" > {icon} - + {dataSource.name}
-
)} -
+
-
- -
+
- +
); diff --git a/frontend/src/HomePage/AppList.jsx b/frontend/src/HomePage/AppList.jsx index 58641cb4c2..0ff33cc6b0 100644 --- a/frontend/src/HomePage/AppList.jsx +++ b/frontend/src/HomePage/AppList.jsx @@ -27,10 +27,10 @@ const AppList = (props) => { )} {!props.isLoading && props.meta.total_count > 0 && (
-
+
{props.apps.map((app) => { return ( -
+
{ - document.body.click(); - }; const { t } = useTranslation(); const Field = ({ text, onClick, customClass }) => { + const closeMenu = () => { + document.body.click(); + onClick(); + }; return (
{ closeMenu(); - onClick(); }} data-cy={`${text.toLowerCase().replace(/\s+/g, '-')}-card-option`} > @@ -44,48 +43,47 @@ export const AppMenu = function AppMenu({ rootClose onToggle={onMenuOpen} overlay={ - - -
- {canUpdateApp && ( - openAppActionModal('change-icon')} - /> - )} - {canCreateApp && ( - <> +
+ + +
+ {canUpdateApp && ( openAppActionModal('add-to-folder')} + text={t('homePage.appCard.changeIcon', 'Change Icon')} + onClick={() => openAppActionModal('change-icon')} /> - - {currentFolder.id && ( + )} + {canCreateApp && ( + <> openAppActionModal('remove-app-from-folder')} + text={t('homePage.appCard.addToFolder', 'Add to folder')} + onClick={() => openAppActionModal('add-to-folder')} /> - )} - - - - )} - {canDeleteApp && ( - - )} -
-
-
+ + {currentFolder.id && ( + openAppActionModal('remove-app-from-folder')} + /> + )} + + + + )} + {canDeleteApp && ( + + )} +
+ + +
} > -
+
-
+
-

+

{t('blankPage.welcomeToToolJet', 'Welcome to your new ToolJet workspace')}

-

+

{t( 'blankPage.getStartedCreateNewApp', 'You can get started by creating a new application or by creating an application using a template in ToolJet Library.' )}

-
+
@@ -142,19 +114,19 @@ export const BlankPage = function BlankPage({
{staticTemplates.map(({ id, name }) => { return ( -
deployApp(id)}> +
deployApp(id)}>

{name} @@ -167,7 +139,7 @@ export const BlankPage = function BlankPage({

diff --git a/frontend/src/HomePage/Configs/AppIcon.json b/frontend/src/HomePage/Configs/AppIcon.json index 7f2a55204c..06bf6d21db 100644 --- a/frontend/src/HomePage/Configs/AppIcon.json +++ b/frontend/src/HomePage/Configs/AppIcon.json @@ -1,19 +1,22 @@ { "iconList": [ "apps", - "business", - "calendar", - "chart-bar", - "code", - "cpu", - "database", - "file-analytics", - "flame", - "presentation", - "report-search", - "settings", - "users", - "world" + "archive", + "floppydisk", + "layers", + "folderupload", + "grid", + "home", + "sentfast", + "server", + "globe", + "share", + "shield", + "sun", + "table", + "menuhome", + "draghandle" + ], "defaultIcon": "apps" } \ No newline at end of file diff --git a/frontend/src/HomePage/ExportAppModal.jsx b/frontend/src/HomePage/ExportAppModal.jsx index a9ed236901..e75297f912 100644 --- a/frontend/src/HomePage/ExportAppModal.jsx +++ b/frontend/src/HomePage/ExportAppModal.jsx @@ -55,7 +55,9 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam return ( closeModal(false)} - contentClassName={`home-modal-component ${customClassName ? ` ${customClassName}` : ''} ${darkMode && 'dark'}`} + contentClassName={`home-modal-component ${customClassName ? ` ${customClassName}` : ''} ${ + darkMode && 'dark-theme' + }`} show={show} size="md" backdrop={true} diff --git a/frontend/src/HomePage/FolderMenu.jsx b/frontend/src/HomePage/FolderMenu.jsx index c49d26057b..d0949ecf1b 100644 --- a/frontend/src/HomePage/FolderMenu.jsx +++ b/frontend/src/HomePage/FolderMenu.jsx @@ -47,7 +47,7 @@ export const FolderMenu = function FolderMenu({ overlay={ diff --git a/frontend/src/HomePage/Folders.jsx b/frontend/src/HomePage/Folders.jsx index d07c0053b4..1e1f962b2d 100644 --- a/frontend/src/HomePage/Folders.jsx +++ b/frontend/src/HomePage/Folders.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import cx from 'classnames'; import { folderService } from '@/_services'; import { toast } from 'react-hot-toast'; @@ -7,6 +7,11 @@ import { FolderMenu } from './FolderMenu'; import { ConfirmDialog } from '@/_components'; import { useTranslation } from 'react-i18next'; import Skeleton from 'react-loading-skeleton'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { BreadCrumbContext } from '@/App/App'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { SearchBox } from '@/_components/SearchBox'; +import _ from 'lodash'; export const Folders = function Folders({ folders, @@ -17,15 +22,11 @@ export const Folders = function Folders({ canCreateFolder, canUpdateFolder, canDeleteFolder, + canCreateApp, darkMode, }) { const [isLoading, setLoadingStatus] = useState(foldersLoading); - const { t } = useTranslation(); - - useEffect(() => { - setLoadingStatus(foldersLoading); - }, [foldersLoading]); - + const [showInput, setShowInput] = useState(false); const [showForm, setShowForm] = useState(false); const [isCreating, setCreationStatus] = useState(false); const [isDeleting, setDeletionStatus] = useState(false); @@ -36,6 +37,31 @@ export const Folders = function Folders({ const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); const [showUpdateForm, setShowUpdateForm] = useState(false); const [activeFolder, setActiveFolder] = useState(currentFolder || {}); + const [filteredData, setFilteredData] = useState(folders); + + const { t } = useTranslation(); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + + useEffect(() => { + setLoadingStatus(foldersLoading); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [foldersLoading]); + + useEffect(() => { + setFilteredData(folders); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [folders]); + + useEffect(() => { + updateSidebarNAV('All apps'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleSearch = (e) => { + const value = e?.target?.value; + const filtered = folders.filter((item) => item?.name?.toLowerCase().includes(value?.toLowerCase())); + setFilteredData(filtered); + }; function saveFolder() { if (validateName()) { @@ -47,6 +73,7 @@ export const Folders = function Folders({ setCreationStatus(false); setShowForm(false); setNewFolderName(''); + handleFolderChange({}); foldersChanged(); }) .catch(({ error }) => { @@ -59,8 +86,13 @@ export const Folders = function Folders({ } function handleFolderChange(folder) { - setActiveFolder(folder); + if (_.isEmpty(folder)) { + setActiveFolder({}); + } else { + setActiveFolder(folder); + } folderChanged(folder); + updateSidebarNAV(folder?.name ?? 'All apps'); } function deleteFolder(folder) { @@ -83,6 +115,7 @@ export const Folders = function Folders({ setShowDeleteConfirmation(false); setDeletionStatus(false); foldersChanged(); + handleFolderChange({}); }) .catch(({ error }) => { toast.error(error); @@ -119,6 +152,7 @@ export const Folders = function Folders({ setUpdationStatus(false); setShowUpdateForm(false); setNewFolderName(''); + updateSidebarNAV(newFolderName); foldersChanged(); }) .catch(({ error }) => { @@ -129,8 +163,15 @@ export const Folders = function Folders({ } } + function handleClose() { + setShowInput(false); + setFilteredData(folders); + } return ( -
+
-
-
- {t('homePage.foldersSection.folders', 'Folders')}{' '} - {!isLoading && folders && folders.length > 0 && `(${folders.length})`} -
- {canCreateFolder && ( -
{ - setNewFolderName(''); - setShowForm(true); - }} - data-cy="create-new-folder-button" - > - {t('homePage.foldersSection.createNewFolder', '+ Create new')} -
+
+ {!showInput ? ( + <> +
+ {t('homePage.foldersSection.filteredData', 'Folders')} + {!isLoading && filteredData && filteredData.length > 0 && `(${filteredData.length})`} +
+
+ {canCreateFolder && ( + <> +
{ + setNewFolderName(''); + setShowForm(true); + }} + data-cy="create-new-folder-button" + > + +
+
{ + setShowInput(true); + }} + > + +
+ + )} +
+ + ) : ( + )}
{!isLoading && ( -
+
handleFolderChange({})} data-cy="all-applications-link" > @@ -183,16 +249,14 @@ export const Folders = function Folders({ )} {isLoading && } {!isLoading && - folders && - folders.length > 0 && - folders.map((folder, index) => ( + filteredData && + filteredData.length > 0 && + filteredData.map((folder, index) => ( handleFolderChange(folder)} data-cy={`${folder.name.toLowerCase().replace(/\s+/g, '-')}-list-card`} > -
+
{`${folder.name}${folder.count > 0 ? ` (${folder.count})` : ''}`}
{(canDeleteFolder || canUpdateFolder) && ( @@ -225,7 +292,7 @@ export const Folders = function Folders({ } >
-
+
setNewFolderName(e.target.value)} @@ -241,22 +308,22 @@ export const Folders = function Folders({
- - +
diff --git a/frontend/src/HomePage/Header.jsx b/frontend/src/HomePage/Header.jsx index ff14c35211..0006373082 100644 --- a/frontend/src/HomePage/Header.jsx +++ b/frontend/src/HomePage/Header.jsx @@ -5,14 +5,15 @@ import { useTranslation } from 'react-i18next'; export default function Header({ onSearchSubmit, darkMode }) { const { t } = useTranslation(); return ( -
+
diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index b96b13b749..c33e431869 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -17,6 +17,9 @@ import { withTranslation } from 'react-i18next'; import { sample } from 'lodash'; import ExportAppModal from './ExportAppModal'; import Footer from './Footer'; +import { OrganizationList } from '@/_components/OrganizationManager/List'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import BulkIcon from '@/_ui/Icon/bulkIcons/index'; import { getWorkspaceId } from '@/_helpers/utils'; import { withRouter } from '@/_hoc/withRouter'; @@ -305,7 +308,6 @@ class HomePageComponent extends React.Component { return; } this.fetchApps(1, this.state.currentFolder.id, key || ''); - this.fetchFolders(key || ''); }; addAppToFolder = () => { @@ -386,7 +388,7 @@ class HomePageComponent extends React.Component { onClick={() => this.setState({ appOperations: { ...appOperations, selectedIcon: icon } })} key={index} > - + )); }; @@ -488,12 +490,15 @@ class HomePageComponent extends React.Component { >
-
- {this.props.t('homePage.appCard.move', 'Move')} - {` "${appOperations?.selectedApp?.name}" `} +
+

+ {this.props.t('homePage.appCard.move', 'Move')} + {` "${appOperations?.selectedApp?.name}" `} +

+ {this.props.t('homePage.appCard.to', 'to')}
-
+
+
+
-
this.addSelectedAppsToGroup(groupPermission.id)} - data-cy={`${groupPermission.group - .toLowerCase() - .replace(/\s+/g, '-')}-group-select-search-add-button`} + data-cy="add-button" + disabled={selectedAppIds?.length == 0} + iconWidth="16" + fill={selectedAppIds.length != 0 ? '#FDFDFE' : this.props.darkMode ? '#4C5155' : '#C1C8CD'} + isLoading={isAddingApps} > - {this.props.t('globals.add', 'Add')} -
+ Add apps +
)}
-
- - - - - - - - +
- {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.name', - 'Name' - )} - - {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.permissions', - 'Permissions' - )} -
+ {groupPermission.group == 'admin' && ( +
+

+ Admin has edit access to all apps. These + are not editable +

+
+ )} +
+

App name

+

Permissions

+
+
{isLoadingGroup || isLoadingApps ? ( ) : ( - appsInGroup.map((app) => ( - - - + + + - - - )) + data-cy="delete-link" + className="delete-link" + > + + Remove + + + )} + + + )) + ) : ( +
+
+ +
+

No apps are added to the group

+ + Add app to the group to control permissions +
for users in this group +
+
+ )} + )}
@@ -425,77 +481,115 @@ class ManageGroupPermissionResourcesComponent extends React.Component {
{app.name} -
- - -
- {this.canAppGroupPermission(app, groupPermission.id, 'view') && ( -
-
{app.name} +
+ + +
+
+ +
+
+ {groupPermission.group !== 'admin' && ( + { + this.removeAppFromGroup(groupPermission.id, app.id, app.name); }} - disabled={groupPermission.group === 'admin'} - checked={this.canAppGroupPermission( - app, - groupPermission.id, - 'hideFromDashboard' - )} - /> - Hide from dashboard - - - )} - - {groupPermission.group !== 'admin' && ( - { - this.removeAppFromGroup(groupPermission.id, app.id, app.name); - }} - className="text-danger" - data-cy="delete-link" - > - {this.props.t('globals.delete', 'Delete')} - - )} -
@@ -507,8 +601,8 @@ class ManageGroupPermissionResourcesComponent extends React.Component {
{groupPermission?.group !== 'all_users' && (
-
- +
-
this.addSelectedUsersToGroup(groupPermission.id, selectedUsers)} - data-cy={`${groupPermission.group - .toLowerCase() - .replace(/\s+/g, '-')}-group-multi-select-search-add-button`} + disabled={selectedUsers.length === 0} + leftIcon="plus" + fill={selectedUsers.length !== 0 ? '#3E63DD' : this.props.darkMode ? '#131620' : '#C1C8CD'} + iconWidth="16" + className="add-users-button" + isLoading={isAddingUsers} > - {this.props.t('globals.add', 'Add')} -
+ Add users +
-
- Selected Users: -
+
Selected Users:
{this.generateSelection(selectedUsers)}
@@ -559,89 +651,100 @@ class ManageGroupPermissionResourcesComponent extends React.Component { )}
-
- - - - - - - - - - {isLoadingGroup || isLoadingUsers ? ( - - - - - - ) : ( - usersInGroup.map((user) => ( - - - - - - )) - )} - -
- {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.name', - 'name' - )} - - {this.props.t( - 'header.organization.menus.manageGroups.permissionResources.email', - 'email' - )} -
-
-
-
-
-
-
-
-
{`${user?.first_name ?? ''} ${user?.last_name ?? ''}`}{user.email} - {groupPermission.group !== 'all_users' && ( - { - this.removeUserFromGroup(groupPermission.id, user.id); - }} - > - {this.props.t('globals.delete', 'Delete')} - - )} -
+ {groupPermission.group == 'all_users' && ( +
+

+ All users include every users in the app. + This list is not editable +

+
+ )} +
+

+ User name +

+

+ Email id +

+

{/* DO NOT REMOVE FOR TABLE ALIGNMENT */}
+
+ {isLoadingGroup || isLoadingUsers ? ( + + +
+
+
+ + +
+ + +
+ + + ) : ( + usersInGroup.map((user) => ( +
+

+

+ {`${user?.first_name?.[0] ?? ''} ${user?.last_name?.[0] ?? ''}`} +
+ {`${user?.first_name ?? ''} ${user?.last_name ?? ''}`} +

+

+ {user.email} +

+

+ {groupPermission.group !== 'all_users' && ( + + { + this.removeUserFromGroup(groupPermission.id, user.id); + }} + > + {this.props.t('globals.delete', 'Delete')} + + + )} +

+
+ )) + )} +
{/* Permissions Tab */} -
+ +
-
+
diff --git a/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx b/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx index beb832064b..a94da87b87 100644 --- a/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx +++ b/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx @@ -1,12 +1,14 @@ import React from 'react'; -import { authenticationService, groupPermissionService } from '@/_services'; +import { groupPermissionService } from '@/_services'; import { ConfirmDialog } from '@/_components'; import { toast } from 'react-hot-toast'; -import { Link } from 'react-router-dom'; import { withTranslation } from 'react-i18next'; import { ManageGroupPermissionResources } from '@/ManageGroupPermissionResources'; import ErrorBoundary from '@/Editor/ErrorBoundary'; - +import Modal from '../HomePage/Modal'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import FolderList from '@/_ui/FolderList/FolderList'; +import { Loader } from '../ManageSSO/Loader'; class ManageGroupPermissionsComponent extends React.Component { constructor(props) { super(props); @@ -24,6 +26,7 @@ class ManageGroupPermissionsComponent extends React.Component { groupToBeUpdated: null, isSaveBtnDisabled: false, selectedGroupPermissionId: null, + selectedGroup: 'All Users', }; } @@ -31,7 +34,15 @@ class ManageGroupPermissionsComponent extends React.Component { this.fetchGroups(); } - fetchGroups = () => { + findCurrentGroupDetails = (data) => { + let currentUpdatedGroup = data.group_permissions.find((item) => { + return item.group == this.state.newGroupName; + }); + this.setState({ selectedGroup: currentUpdatedGroup.group }); + return currentUpdatedGroup.id; + }; + + fetchGroups = (type = 'admin') => { this.setState({ isLoading: true, }); @@ -42,7 +53,12 @@ class ManageGroupPermissionsComponent extends React.Component { this.setState({ groups: data.group_permissions, isLoading: false, - selectedGroupPermissionId: data.group_permissions[0].id, + selectedGroupPermissionId: + type == 'admin' + ? data.group_permissions[0].id + : type == 'current' + ? this.findCurrentGroupDetails(data) + : data.group_permissions.at(-1).id, }); }) .catch(({ error }) => { @@ -69,8 +85,10 @@ class ManageGroupPermissionsComponent extends React.Component { switch (groupName) { case 'all_users': return 'All Users'; + case 'admin': return 'Admin'; + default: return groupName; } @@ -85,9 +103,10 @@ class ManageGroupPermissionsComponent extends React.Component { creatingGroup: false, showNewGroupForm: false, newGroupName: null, + selectedGroup: this.state.newGroupName, }); toast.success('Group has been created'); - this.fetchGroups(); + this.fetchGroups('new'); }) .catch(({ error }) => { toast.error(error); @@ -129,6 +148,7 @@ class ManageGroupPermissionsComponent extends React.Component { .then(() => { toast.success('Group deleted successfully'); this.fetchGroups(); + this.setState({ selectedGroup: 'All Users', isDeletingGroup: false }); }) .catch(({ error }) => { toast.error(error); @@ -139,17 +159,16 @@ class ManageGroupPermissionsComponent extends React.Component { }; executeGroupUpdation = () => { - this.setState({ isUpdatingGroupName: true }); + this.setState({ isUpdatingGroupName: true, selectedGroup: this.state.newGroupName }); groupPermissionService .update(this.state.groupToBeUpdated?.id, { name: this.state.newGroupName }) .then(() => { toast.success('Group name updated successfully'); - this.fetchGroups(); + this.fetchGroups('current'); this.setState({ isUpdatingGroupName: false, groupToBeUpdated: null, showGroupNameUpdateForm: false, - newGroupName: null, }); }) .catch(({ error }) => { @@ -174,217 +193,156 @@ class ManageGroupPermissionsComponent extends React.Component { return (
- this.executeGroupDeletion()} - onCancel={() => this.cancelDeleteGroupDialog()} - darkMode={this.props.darkMode} - /> - -
-
-
-
-
-
-

- {this.props.t('header.organization.menus.manageGroups.permissions.userGroups', 'User Groups')} -

-
-
- {!showNewGroupForm && !showGroupNameUpdateForm && ( -
this.setState({ showNewGroupForm: true, isSaveBtnDisabled: true })} - data-cy="create-new-group-button" - > - {this.props.t( - 'header.organization.menus.manageGroups.permissions.createNewGroup', - 'Create new group' - )} -
- )} -
-
-
+
+ this.executeGroupDeletion()} + onCancel={() => this.cancelDeleteGroupDialog()} + darkMode={this.props.darkMode} + /> +
+

{groups?.length} Groups

+ {!showNewGroupForm && !showGroupNameUpdateForm && ( + { + e.preventDefault(); + this.setState({ newGroupName: null, showNewGroupForm: true, isSaveBtnDisabled: true }); + }} + data-cy="create-new-group-button" + leftIcon="plus" + isLoading={isLoading} + iconWidth="16" + fill={'#FDFDFE'} + > + {this.props.t( + 'header.organization.menus.manageGroups.permissions.createNewGroup', + 'Create new group' + )} + + )}
-
- {(showNewGroupForm || showGroupNameUpdateForm) && ( -
-
-
-

- {showGroupNameUpdateForm - ? this.props.t( - 'header.organization.menus.manageGroups.permissions.updateGroup', - 'Update group' - ) - : this.props.t( - 'header.organization.menus.manageGroups.permissions.addNewGroup', - 'Add new group' - )} -

-
-
- { - e.preventDefault(); - if (showNewGroupForm) { - this.createGroup(); - } else { - this.executeGroupUpdation(); - } + + this.setState({ + showNewGroupForm: false, + showGroupNameUpdateForm: false, + newGroupName: null, + }) + } + title={ + showGroupNameUpdateForm + ? this.props.t('header.organization.menus.manageGroups.permissions.updateGroup', 'Update group') + : this.props.t('header.organization.menus.manageGroups.permissions.addNewGroup', 'Add new group') + } + > + { + e.preventDefault(); + if (showNewGroupForm) { + this.createGroup(); + } else { + this.executeGroupUpdation(); + } + }} + > +
+
+
+ { + this.changeNewGroupName(e.target.value); }} - > -
-
-
- { - this.changeNewGroupName(e.target.value); - }} - value={this.state.newGroupName} - data-cy="group-name-input" - /> -
-
-
-
- - -
- + value={this.state.newGroupName} + data-cy="group-name-input" + autoFocus + />
- )} - {!showNewGroupForm && !showGroupNameUpdateForm && ( -
-
-
- - - - - - - - - {isLoading ? ( - - {Array.from(Array(2)).map((index) => ( - - - - - - ))} - - ) : ( - - {groups.map((permissionGroup) => ( - - - - - - ))} - - )} -
- {this.props.t('header.organization.menus.manageGroups.permissions.name', 'Name')} -
-
-
-
-
-
-
-
-
this.setState({ selectedGroupPermissionId: permissionGroup.id })} - data-cy={`${this.humanizeifDefaultGroupName(permissionGroup.group) - .toLowerCase() - .replace(/\s+/g, '-')}-group-link`} - > - {this.humanizeifDefaultGroupName(permissionGroup.group)} - - {permissionGroup.group !== 'admin' && permissionGroup.group !== 'all_users' && ( -
- this.updateGroupName(permissionGroup)} - data-cy={`${this.humanizeifDefaultGroupName(permissionGroup.group) - .toLowerCase() - .replace(/\s+/g, '-')}-group-update-link`} - > - {this.props.t('globals.update', 'Update')} - - this.deleteGroup(permissionGroup.id)} - data-cy={`${this.humanizeifDefaultGroupName(permissionGroup.group) - .toLowerCase() - .replace(/\s+/g, '-')}-group-delete-link`} - > - {this.props.t('globals.delete', 'Delete')} - -
- )} -
-
-
-
+
+ + this.setState({ + showNewGroupForm: false, + showGroupNameUpdateForm: false, + newGroupName: null, + }) + } + disabled={creatingGroup} + data-cy="cancel-button" + variant="tertiary" + > + {this.props.t('globals.cancel', 'Cancel')} + + + {showGroupNameUpdateForm + ? this.props.t('globals.save', 'Save') + : this.props.t('header.organization.menus.manageGroups.permissions.createGroup', 'Create Group')} + +
+ + + + {!showNewGroupForm && !showGroupNameUpdateForm && ( +
+
+ {groups.map((permissionGroup) => { + return ( + { + this.setState({ + selectedGroupPermissionId: permissionGroup.id, + selectedGroup: this.humanizeifDefaultGroupName(permissionGroup.group), + }); + }} + className="groups-folder-list" + > + {this.humanizeifDefaultGroupName(permissionGroup.group)} + + ); + })} +
+ +
+ {isLoading ? ( + + ) : ( -
+ )}
- )} -
+
+ )}
diff --git a/frontend/src/ManageOrgUsers/FileDropzone.jsx b/frontend/src/ManageOrgUsers/FileDropzone.jsx new file mode 100644 index 0000000000..acf1e13c81 --- /dev/null +++ b/frontend/src/ManageOrgUsers/FileDropzone.jsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import { useDropzone } from 'react-dropzone'; +import BulkIcon from '@/_ui/Icon/BulkIcons'; +import { toast } from 'react-hot-toast'; + +export function FileDropzone({ handleClick, hiddenFileInput, errors, handleFileChange, inviteBulkUsers, onDrop }) { + const { getRootProps, getInputProps, isDragActive, acceptedFiles } = useDropzone({ + onDrop, + accept: 'text/csv', + }); + const [fileData, setFileData] = useState(); + + const files = + acceptedFiles.length > 0 + ? acceptedFiles + : acceptedFiles?.map((file) => ( +
  • + {file.path} - {file.size} bytes +
  • + )); + return ( +
    +
    +
    +
    + +
    +

    + Select a CSV file to upload +

    + + {!isDragActive ? 'Or drag and drop it here' : ''} + + { + const file = e.target.files[0]; + setFileData(file); + if (Math.round(file.size / 1024) > 1024) { + toast.error('File size cannot exceed more than 1MB'); + e.target.value = null; + } else { + handleFileChange(file); + } + }} + accept=".csv" + type="file" + className="form-control" + data-cy="input-field-bulk-upload" + /> + + {errors['file']} + +
      {files}
    + {fileData?.name &&
      {` ${fileData?.name} - ${fileData?.size} bytes`}
    } +
    +
    +
    + ); +} diff --git a/frontend/src/ManageOrgUsers/InviteUsersForm.jsx b/frontend/src/ManageOrgUsers/InviteUsersForm.jsx new file mode 100644 index 0000000000..f68cba448d --- /dev/null +++ b/frontend/src/ManageOrgUsers/InviteUsersForm.jsx @@ -0,0 +1,204 @@ +import React, { useState, useCallback, useRef } from 'react'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { toast } from 'react-hot-toast'; +// import { useDropzone } from 'react-dropzone'; +import { FileDropzone } from './FileDropzone'; + +function InviteUsersForm({ + onClose, + createUser, + changeNewUserOption, + errors, + fields, + handleFileChange, + uploadingUsers, + onCancel, + inviteBulkUsers, +}) { + const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState(1); + + const hiddenFileInput = useRef(null); + // const { acceptedFiles } = useDropzone({ + // onDrop, + // accept: 'text/csv', + // }); + + const onDrop = useCallback((acceptedFiles) => { + const file = acceptedFiles[0]; + if (Math.round(file.size / 1024) > 1024) { + toast.error('File size cannot exceed more than 1MB'); + } else { + handleFileChange(file); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleClick = () => { + hiddenFileInput.current.click(); + }; + + // const files = acceptedFiles.map((file) => ( + //
  • + // {file.path} - {file.size} bytes + //
  • + // )); + + return ( +
    +
    +
    +
    +
    +

    + {t('header.organization.menus.manageUsers.addNewUser', 'Add new user')} +

    +
    { + onCancel(); + onClose(); + }} + style={{ cursor: 'pointer' }} + data-cy="close-button" + > + +
    +
    +
    +
    + + +
    +
    +
    + {activeTab == 1 ? ( +
    +
    +
    + +
    +
    + + + {errors['fullName']} + +
    +
    +
    + +
    + + + {errors['email']} + +
    +
    +
    +
    +
    + ) : ( +
    +
    +
    +
    + +
    +
    +

    + Download the ToolJet template to add user details or format your file in the same as the template. + ToolJet won’t be able to recognise files in any other format.{' '} +

    + + Download Template + +
    +
    +
    + +
    + )} +
    + { + onCancel(); + onClose(); + }} + variant="tertiary" + > + {t('globals.cancel', 'Cancel')} + + + + {activeTab == 1 ? t('header.organization.menus.manageUsers.inviteUsers', 'Invite Users') : 'Upload users'} + +
    +
    +
    +
    + ); +} +export default InviteUsersForm; diff --git a/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx b/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx index 23ce5e8de4..0b8fdbf676 100644 --- a/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx +++ b/frontend/src/ManageOrgUsers/ManageOrgUsers.jsx @@ -7,6 +7,8 @@ import urlJoin from 'url-join'; import ErrorBoundary from '@/Editor/ErrorBoundary'; import UsersTable from '../../ee/components/UsersPage/UsersTable'; import UsersFilter from '../../ee/components/UsersPage/UsersFilter'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import ManageOrgUsersDrawer from './ManageOrgUsersDrawer'; class ManageOrgUsersComponent extends React.Component { constructor(props) { @@ -14,8 +16,6 @@ class ManageOrgUsersComponent extends React.Component { this.state = { isLoading: true, - showNewUserForm: false, - showUploadUserForm: false, creatingUser: false, uploadingUsers: false, newUser: {}, @@ -29,6 +29,7 @@ class ManageOrgUsersComponent extends React.Component { currentPage: 1, options: {}, file: null, + isInviteUsersDrawerOpen: false, }; } @@ -41,11 +42,8 @@ class ManageOrgUsersComponent extends React.Component { handleValidation() { let fields = this.state.fields; let errors = {}; - if (!fields['firstName']) { - errors['firstName'] = 'This field is required'; - } - if (!fields['lastName']) { - errors['lastName'] = 'This field is required'; + if (!fields['fullName']) { + errors['fullName'] = 'This field is required'; } if (!fields['email']) { errors['email'] = 'This field is required'; @@ -67,10 +65,6 @@ class ManageOrgUsersComponent extends React.Component { return Object.keys(errors).length === 0; } - componentDidMount() { - this.fetchUsers(1); - } - fetchUsers = (page = 1, options = {}) => { this.setState({ options, @@ -146,7 +140,7 @@ class ManageOrgUsersComponent extends React.Component { this.fetchUsers(); this.setState({ uploadingUsers: false, - showUploadUserForm: false, + isInviteUsersDrawerOpen: false, file: null, }); }) @@ -161,15 +155,25 @@ class ManageOrgUsersComponent extends React.Component { this.setState({ file }); }; + handleNameSplit = (fullName) => { + const [first, last] = fullName.split(' '); + let fields = this.state.fields; + fields['firstName'] = first; + fields['lastName'] = last; + this.setState({ + fields, + }); + }; + createUser = (event) => { event.preventDefault(); if (this.handleValidation()) { - if (!this.state.fields.firstName?.trim() || !this.state.fields.lastName?.trim()) { - toast.error('First and last name should not be empty'); + if (!this.state.fields.fullName?.trim()) { + toast.error('Name should not be empty'); return; } - + this.handleNameSplit(this.state.fields['fullName']); let fields = {}; Object.keys(this.state.fields).map((key) => { fields[key] = ''; @@ -191,8 +195,8 @@ class ManageOrgUsersComponent extends React.Component { this.fetchUsers(); this.setState({ creatingUser: false, - showNewUserForm: false, fields: fields, + isInviteUsersDrawerOpen: false, }); }) .catch(({ error }) => { @@ -200,7 +204,7 @@ class ManageOrgUsersComponent extends React.Component { this.setState({ creatingUser: false }); }); } else { - this.setState({ creatingUser: false, showNewUserForm: true, file: null }); + this.setState({ creatingUser: false, file: null, isInviteUsersDrawerOpen: true }); } }; @@ -228,249 +232,80 @@ class ManageOrgUsersComponent extends React.Component { filterList = (options) => { this.fetchUsers(1, options); }; + setIsInviteUsersDrawerOpen = (val) => { + this.setState({ isInviteUsersDrawerOpen: val }); + }; + + onCancel = () => { + this.setState({ + errors: {}, + file: null, + fields: {}, + }); + }; render() { - const { - isLoading, - showNewUserForm, - showUploadUserForm, - creatingUser, - uploadingUsers, - users, - archivingUser, - unarchivingUser, - meta, - } = this.state; + const { isLoading, uploadingUsers, users, archivingUser, unarchivingUser, meta } = this.state; return (
    + {this.state.isInviteUsersDrawerOpen && ( + + )} +
    -
    -
    -
    -
    -
    -

    - {this.props.t('header.organization.menus.manageUsers.usersAndPermission', 'Users & Permissions')} -

    +
    +
    +
    +
    + {meta?.total_count} users
    -
    - {!showUploadUserForm && !showNewUserForm && ( -
    this.setState({ showUploadUserForm: true })} - data-cy="invite-bulk-user-button" - > - Invite bulk users -
    - )} - {!showNewUserForm && !showUploadUserForm && ( -
    this.setState({ showNewUserForm: true })} - data-cy="invite-new-user" - > - {this.props.t('header.organization.menus.manageUsers.inviteNewUser', 'Invite new user')} -
    - )} +
    + this.setState({ isInviteUsersDrawerOpen: true })} + leftIcon="usergroup" + fill={'#FDFDFE'} + > + {this.props.t('header.organization.menus.manageUsers.addNewUser', 'Add users')} +
    - {showNewUserForm && ( -
    -
    -
    -

    - {this.props.t('header.organization.menus.manageUsers.addNewUser', 'Add new user')} -

    -
    -
    -
    -
    -
    -
    - - - {this.state.errors['firstName']} - -
    -
    - - - {this.state.errors['lastName']} - -
    -
    -
    -
    - -
    - - - {this.state.errors['email']} - -
    -
    -
    - - -
    -
    -
    + this.fetchUsers()} + /> + + {users?.length === 0 && ( +
    +
    + + No result found + + + Try changing the filters +
    )} - {showUploadUserForm && ( -
    -
    -
    -

    - Upload Users -

    -
    -
    -
    -
    -
    -
    - { - const file = e.target.files[0]; - if (Math.round(file.size / 1024) > 1024) { - toast.error('File size cannot exceed more than 1MB'); - e.target.value = null; - } else { - this.handleFileChange(file); - } - }} - accept=".csv" - type="file" - className="form-control" - data-cy="bulk-user-upload-input" - /> - - {this.state.errors['file']} - -
    - -
    -
    -
    - - -
    -
    -
    -
    -
    - )} - - {!showNewUserForm && !showUploadUserForm && ( - this.fetchUsers()} - /> - )} - - {users?.length === 0 && !showNewUserForm && !showUploadUserForm && ( -
    - No result found - Try changing the filters -
    - )} - - {!showNewUserForm && !showUploadUserForm && users?.length !== 0 && ( + {users?.length !== 0 && ( { + return ( + setIsInviteUsersDrawerOpen(false)} + position="right" + > + setIsInviteUsersDrawerOpen(false)} + /> + + ); +}; + +export default ManageOrgUsersDrawer; diff --git a/frontend/src/ManageOrgVars/ManageOrgVars.jsx b/frontend/src/ManageOrgVars/ManageOrgVars.jsx index bc0aba199c..028017d084 100644 --- a/frontend/src/ManageOrgVars/ManageOrgVars.jsx +++ b/frontend/src/ManageOrgVars/ManageOrgVars.jsx @@ -2,10 +2,11 @@ import React from 'react'; import { authenticationService, orgEnvironmentVariableService } from '@/_services'; import { ConfirmDialog } from '@/_components'; import { toast } from 'react-hot-toast'; -import VariableForm from './VariableForm'; import VariablesTable from './VariablesTable'; // eslint-disable-next-line import/no-unresolved import { withTranslation } from 'react-i18next'; +import ManageOrgVarsDrawer from './ManageOrgVarsDrawer'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; class ManageOrgVarsComponent extends React.Component { constructor(props) { super(props); @@ -22,6 +23,7 @@ class ManageOrgVarsComponent extends React.Component { }, errors: {}, showVariableDeleteConfirmation: false, + isManageVarDrawerOpen: false, }; this.tableRef = React.createRef(null); @@ -34,6 +36,7 @@ class ManageOrgVarsComponent extends React.Component { onEditBtnClicked = (variable) => { this.setState({ showVariableForm: true, + isManageVarDrawerOpen: true, errors: {}, fields: { ...variable, @@ -50,6 +53,7 @@ class ManageOrgVarsComponent extends React.Component { onCancelBtnClicked = () => { this.setState({ showVariableForm: false, + isManageVarDrawerOpen: false, newVariable: {}, fields: { encryption: false, variable_type: 'client' }, selectedVariableId: null, @@ -135,6 +139,7 @@ class ManageOrgVarsComponent extends React.Component { this.setState({ addingVar: false, showVariableForm: false, + isManageVarDrawerOpen: false, fields: fields, selectedVariableId: null, }); @@ -159,6 +164,7 @@ class ManageOrgVarsComponent extends React.Component { this.setState({ addingVar: false, showVariableForm: false, + isManageVarDrawerOpen: false, fields: fields, selectedVariableId: null, }); @@ -169,7 +175,7 @@ class ManageOrgVarsComponent extends React.Component { }); } } else { - this.setState({ addingVar: false, showVariableForm: true }); + this.setState({ addingVar: false, showVariableForm: true, isManageVarDrawerOpen: true }); } }; @@ -239,9 +245,12 @@ class ManageOrgVarsComponent extends React.Component { authenticationService.currentSessionValue.group_permissions ); }; + setIsManageVarDrawerOpen = (val) => { + this.setState({ isManageVarDrawerOpen: val }); + }; render() { - const { isLoading, showVariableForm, addingVar, variables } = this.state; + const { isLoading, addingVar, variables, isManageVarDrawerOpen } = this.state; return (
    -
    -
    -
    -
    -

    - {this.props.t('globals.environmentVar', 'Workspace Variables')} -

    -
    -
    - {!showVariableForm && this.canCreateVariable() && ( -
    this.setState({ showVariableForm: true, errors: {} })} +
    +
    +
    + {!isManageVarDrawerOpen && this.canCreateVariable() && ( + this.setState({ isManageVarDrawerOpen: true, errors: {} })} + className="add-new-variables-button" > {this.props.t( 'header.organization.menus.manageSSO.environmentVar.addNewVariable', 'Add new variable' )} -
    + )}
    @@ -291,8 +295,10 @@ class ManageOrgVarsComponent extends React.Component {
    - {showVariableForm ? ( - { + return ( + setIsManageVarDrawerOpen(false)} + position="right" + > + + + ); +}; + +export default ManageOrgVarsDrawer; diff --git a/frontend/src/ManageOrgVars/VariableForm.jsx b/frontend/src/ManageOrgVars/VariableForm.jsx index 879fc9620b..d47734f8cf 100644 --- a/frontend/src/ManageOrgVars/VariableForm.jsx +++ b/frontend/src/ManageOrgVars/VariableForm.jsx @@ -1,6 +1,7 @@ import React from 'react'; import Select from '@/_ui/Select'; import { withTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; class VariableForm extends React.Component { constructor(props) { super(props); @@ -8,140 +9,133 @@ class VariableForm extends React.Component { render() { return ( -
    -
    -
    -

    - {!this.props.selectedVariableId - ? this.props.t( - 'header.organization.menus.manageSSO.environmentVar.variableForm.addNewVariable', - 'Add new variable' - ) - : this.props.t( - 'header.organization.menus.manageSSO.environmentVar.variableForm.updatevariable', - 'Update variable' - )} -

    -
    -
    -
    -
    -
    -
    - - - - {this.props.errors['variable_name']} - -
    -
    - - - - {this.props.errors['value']} - -
    -
    -
    -
    -
    -
    - - {this.props.selectedVariableId ? ( - {this.props.fields['variable_type']} - ) : ( - -
    - -
    - this.props.handleEncryptionToggle(e)} - checked={ - this.props.fields['variable_type'] === 'server' ? true : this.props.fields['encryption'] - } - data-cy="enable-toggle" - /> -
    + name="variable_name" + onChange={this.props.changeNewVariableOption.bind(this, 'variable_name')} + value={this.props.fields['variable_name']} + data + autoFocus + data-cy="workspace-variable-name-input" + /> + + {this.props.errors['variable_name']} + +
    +
    + + + + {this.props.errors['value']} + +
    +
    +
    +
    +
    +
    + + {this.props.selectedVariableId ? ( + {this.props.fields['variable_type']} + ) : ( + this.props.handleEncryptionToggle(e)} + checked={this.props.fields['variable_type'] === 'server' ? true : this.props.fields['encryption']} + />
    -
    - - -
    - -
    +
    + +
    +
    + this.props.onCancelBtnClicked()} data-cy="cancel-button" variant="tertiary"> + {this.props.t('globals.cancel', 'Cancel')} + + + {' '} + {!this.props.selectedVariableId + ? this.props.t( + 'header.organization.menus.manageSSO.environmentVar.variableForm.addVariable', + 'Add variable' + ) + : this.props.t('globals.save', 'Save')} +
    ); diff --git a/frontend/src/ManageOrgVars/VariablesTable.jsx b/frontend/src/ManageOrgVars/VariablesTable.jsx index 32b053f455..0ec241a88c 100644 --- a/frontend/src/ManageOrgVars/VariablesTable.jsx +++ b/frontend/src/ManageOrgVars/VariablesTable.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { withTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; import { Tooltip } from 'react-tooltip'; class VariablesTable extends React.Component { @@ -18,7 +19,7 @@ class VariablesTable extends React.Component { const { isLoading, variables } = this.props; return (
    -
    +
    @@ -134,15 +135,7 @@ class VariablesTable extends React.Component { data-tooltip-content="Update" >
    - +
    @@ -156,19 +149,9 @@ class VariablesTable extends React.Component { data-cy={`${variable.variable_name .toLowerCase() .replace(/\s+/g, '-')}-workspace-variable-delete-button`} - data-tooltip-id="tooltip-for-delete" - data-tooltip-content="Delete" >
    - +
    diff --git a/frontend/src/ManageSSO/Form.jsx b/frontend/src/ManageSSO/Form.jsx index a1d6fd2d7d..8c29743683 100644 --- a/frontend/src/ManageSSO/Form.jsx +++ b/frontend/src/ManageSSO/Form.jsx @@ -27,7 +27,7 @@ export function Form({ settings, updateData, darkMode }) { }; return ( -
    +
    {t('header.organization.menus.manageSSO.passwordLogin', 'Password Login')} - + {enabled ? t('globals.enabled', 'Enabled') : t('globals.disabled', 'Disabled')}
    diff --git a/frontend/src/ManageSSO/GeneralSettings.jsx b/frontend/src/ManageSSO/GeneralSettings.jsx index fa5b688910..f8d2234785 100644 --- a/frontend/src/ManageSSO/GeneralSettings.jsx +++ b/frontend/src/ManageSSO/GeneralSettings.jsx @@ -3,13 +3,36 @@ import React, { useState } from 'react'; import { toast } from 'react-hot-toast'; import { copyToClipboard } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { ConfirmDialog } from '@/_components'; -export function GeneralSettings({ settings, updateData, instanceSettings }) { +export function GeneralSettings({ settings, updateData, instanceSettings, darkMode }) { const [enableSignUp, setEnableSignUp] = useState(settings?.enable_sign_up || false); const [inheritSSO, setInheritSSO] = useState(settings?.inherit_s_s_o || false); const [domain, setDomain] = useState(settings?.domain || ''); const [isSaving, setSaving] = useState(false); const { t } = useTranslation(); + const passwordSettings = settings?.sso_configs?.find((obj) => obj.sso === 'form'); + const [enabled, setEnabled] = useState(passwordSettings?.enabled || false); + const [showDisablingPasswordConfirmation, setShowDisablingPasswordConfirmation] = useState(false); + + const changeStatus = () => { + organizationService.editOrganizationConfigs({ type: 'form', enabled: !enabled }).then( + (data) => { + const enabled_tmp = !enabled; + setEnabled(enabled_tmp); + updateData('form', { id: data.id, enabled: enabled_tmp }); + toast.success(`${enabled_tmp ? 'Enabled' : 'Disabled'} Password login`, { position: 'top-center' }); + setShowDisablingPasswordConfirmation(false); + }, + () => { + toast.error('Error while saving SSO configurations', { + position: 'top-center', + }); + } + ); + }; const reset = () => { setEnableSignUp(settings?.enable_sign_up || false); @@ -39,65 +62,23 @@ export function GeneralSettings({ settings, updateData, instanceSettings }) { ); }; - const tickIcon = () => { - return ( - - - - - ); - }; - - const crossIcon = () => { - return ( - - - - - - ); - }; - const ssoButtons = (type) => { return ( -
    -
    {inheritSSO ? tickIcon() : crossIcon()}
    +
    ); }; return ( -
    +
    {t('header.organization.menus.manageSSO.generalSettings.title', 'General Settings')}
    -
    +
    -
    - {instanceSettings.google.enabled && ssoButtons('google')} - {instanceSettings.git.enabled && ssoButtons('git')} -
    -
    -
    +
    +
    {t( 'header.organization.menus.manageSSO.generalSettings.ssoAuth', `Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO.` )}
    +
    + +

    Default options

    +
    + {instanceSettings.google.enabled && ssoButtons('google')} + {instanceSettings.git.enabled && ssoButtons('git')} +
    +
    )} -
    +
    -
    - setDomain(e.target.value)} - data-cy="allowed-domain-input" - /> -
    -
    -
    - {t( - 'header.organization.menus.manageSSO.generalSettings.supportMultiDomains', - `Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com` - )} -
    + setDomain(e.target.value)} + data-cy="allowed-domain-input" + /> +
    +
    +
    + {t( + 'header.organization.menus.manageSSO.generalSettings.supportMultiDomains', + `Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com` + )}
    @@ -177,20 +164,13 @@ export function GeneralSettings({ settings, updateData, instanceSettings }) { {t('header.organization.menus.manageSSO.generalSettings.loginUrl', `Login URL`)} -
    +

    {`${window.public_config?.TOOLJET_HOST}/login/${authenticationService?.currentSessionValue?.current_organization_id}`}

    - copyFunction('login-url')} - src={`assets/images/icons/copy-dark.svg`} - width="22" - height="22" - className="sso-copy" - data-cy="copy-icon" - /> + copyFunction('login-url')} />
    -
    +
    {t( 'header.organization.menus.manageSSO.generalSettings.workspaceLogin', @@ -199,22 +179,66 @@ export function GeneralSettings({ settings, updateData, instanceSettings }) {
    -
    - - + + changeStatus()} + onCancel={() => setShowDisablingPasswordConfirmation(false)} + darkMode={darkMode} + /> +
    +
    + +

    + Danger zone +

    +
    +
    + +
    +
    + Disable password login only if your SSO is configured otherwise you will get logged out. +
    +
    +
    + +
    + + {t('globals.cancel', 'Cancel')} + + + + {t('globals.savechanges', 'Save')} + +
    ); } diff --git a/frontend/src/ManageSSO/Git.jsx b/frontend/src/ManageSSO/Git.jsx index 77869a2a7b..8a18d0d0ce 100644 --- a/frontend/src/ManageSSO/Git.jsx +++ b/frontend/src/ManageSSO/Git.jsx @@ -3,6 +3,9 @@ import { organizationService } from '@/_services'; import { toast } from 'react-hot-toast'; import { copyToClipboard } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import Toggle from '@/_ui/Toggle/index'; export function Git({ settings, updateData }) { const [enabled, setEnabled] = useState(settings?.enabled || false); @@ -69,35 +72,31 @@ export function Git({ settings, updateData }) { }; return ( -
    +
    -
    - {t('header.organization.menus.manageSSO.github.title', 'Github')} - +
    + +
    +
    + {enabled ? t('globals.enabled', 'Enabled') : t('globals.disabled', 'Disabled')}
    -
    - -
    -
    +
    -
    +
    -
    -
    +
    +
    {t('header.organization.menus.manageSSO.github.requiredGithub', 'Required if GitHub is self hosted')}
    @@ -117,7 +116,7 @@ export function Git({ settings, updateData }) { -
    +
    -
    +
    {t('header.organization.menus.manageSSO.github.redirectUrl', 'Redirect URL')} -
    +

    {`${window.public_config?.TOOLJET_HOST}/sso/git/${configId}`}

    - copyFunction('redirect-url')} - src={`assets/images/icons/copy-dark.svg`} - width="22" - height="22" - className="sso-copy" - /> + copyFunction('redirect-url')} />
    )} -
    - - -
    +
    + + {t('globals.cancel', 'Cancel')} + + + + {t('globals.savechanges', 'Save changes')} + +
    ); } diff --git a/frontend/src/ManageSSO/Google.jsx b/frontend/src/ManageSSO/Google.jsx index 77a0e2691e..b8e83327ac 100644 --- a/frontend/src/ManageSSO/Google.jsx +++ b/frontend/src/ManageSSO/Google.jsx @@ -3,6 +3,8 @@ import { organizationService } from '@/_services'; import { toast } from 'react-hot-toast'; import { copyToClipboard } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; export function Google({ settings, updateData }) { const [enabled, setEnabled] = useState(settings?.enabled || false); @@ -61,17 +63,9 @@ export function Google({ settings, updateData }) { }; return ( -
    +
    -
    - {t('header.organization.menus.manageSSO.google.title', 'Google')} - - {enabled - ? t('header.organization.menus.manageSSO.google.enabled', 'Enabled') - : t('header.organization.menus.manageSSO.google.disabled', 'Disabled')} - -
    + +
    + + {enabled + ? t('header.organization.menus.manageSSO.google.enabled', 'Enabled') + : t('header.organization.menus.manageSSO.google.disabled', 'Disabled')} + +
    -
    +
    -
    +
    {t('header.organization.menus.manageSSO.google.redirectUrl', 'Redirect URL')} -
    +

    {`${window.public_config?.TOOLJET_HOST}/sso/google/${configId}`}

    - copyFunction('redirect-url')} - src={`assets/images/icons/copy-dark.svg`} - width="22" - height="22" - className="sso-copy" - /> + copyFunction('redirect-url')} />
    )} -
    - - -
    +
    + + {t('globals.cancel', 'Cancel')} + + + + {t('globals.savechanges', 'Save changes')} + +
    ); } diff --git a/frontend/src/ManageSSO/Loader.jsx b/frontend/src/ManageSSO/Loader.jsx index da9fb268c5..d5177ee0ef 100644 --- a/frontend/src/ManageSSO/Loader.jsx +++ b/frontend/src/ManageSSO/Loader.jsx @@ -2,7 +2,7 @@ import React from 'react'; export function Loader() { return ( -
    +
    diff --git a/frontend/src/ManageSSO/ManageSSO.jsx b/frontend/src/ManageSSO/ManageSSO.jsx index 1b4e180755..ba3334da47 100644 --- a/frontend/src/ManageSSO/ManageSSO.jsx +++ b/frontend/src/ManageSSO/ManageSSO.jsx @@ -1,24 +1,20 @@ import React, { useState, useCallback, useEffect } from 'react'; import { organizationService } from '@/_services'; -import { Menu } from '@/_components'; import { GeneralSettings } from './GeneralSettings'; import { Google } from './Google'; import { Loader } from './Loader'; import { Git } from './Git'; -import { Form } from './Form'; // eslint-disable-next-line import/no-unresolved -import { useTranslation } from 'react-i18next'; import ErrorBoundary from '@/Editor/ErrorBoundary'; import { toast } from 'react-hot-toast'; +import FolderList from '@/_ui/FolderList/FolderList'; export function ManageSSO({ darkMode }) { const menuItems = [ { id: 'general-settings', label: 'General Settings' }, { id: 'google', label: 'Google' }, { id: 'git', label: 'GitHub' }, - { id: 'form', label: 'Password Login' }, ]; - const { t } = useTranslation(); const changePage = useCallback( (page) => { setCurrentPage(page); @@ -33,19 +29,18 @@ export function ManageSSO({ darkMode }) { const showPage = () => { switch (currentPage) { case 'general-settings': - return ; + return ( + + ); case 'google': return obj.sso === 'google')} />; case 'git': return obj.sso === 'git')} />; - case 'form': - return ( -
    obj.sso === 'form')} - darkMode={darkMode} - /> - ); default: return ; } @@ -101,27 +96,28 @@ export function ManageSSO({ darkMode }) {
    -
    -
    -
    -
    -

    - {t('header.organization.menus.manageSSO.manageSso', 'SSO')} -

    +
    +
    +
    +
      + {menuItems.map((item, index) => { + return ( + changePage(item.id)} + key={index} + selectedItem={currentPage == item.id} + items={menuItems} + onChange={changePage} + isLoading={isLoading} + dataCy={`${String(item.label).toLowerCase().replace(/\s+/g, '-')}`} + > + {item.label} + + ); + })} +
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    {showPage()}
    +
    {showPage()}
    diff --git a/frontend/src/MarketplacePage/InstalledPlugins.jsx b/frontend/src/MarketplacePage/InstalledPlugins.jsx index 3efef03ec3..1066c8552c 100644 --- a/frontend/src/MarketplacePage/InstalledPlugins.jsx +++ b/frontend/src/MarketplacePage/InstalledPlugins.jsx @@ -120,7 +120,7 @@ const InstalledPluginCard = ({ plugin, marketplacePlugin, fetchPlugins, isDevMod darkMode={darkMode} />
    -
    +
    diff --git a/frontend/src/MarketplacePage/MarketplaceCard.jsx b/frontend/src/MarketplacePage/MarketplaceCard.jsx index cbfc48d3f4..5a6146cdde 100644 --- a/frontend/src/MarketplacePage/MarketplaceCard.jsx +++ b/frontend/src/MarketplacePage/MarketplaceCard.jsx @@ -44,11 +44,11 @@ export const MarketplaceCard = ({ id, name, repo, description, version, isInstal return (
    -
    +
    - +
    @@ -63,7 +63,7 @@ export const MarketplaceCard = ({ id, name, repo, description, version, isInstal v{version}
    -
    Install{installed && 'ed'}
    +
    Install{installed && 'ed'}
    diff --git a/frontend/src/MarketplacePage/index.jsx b/frontend/src/MarketplacePage/index.jsx index 0a3e995b12..25a66817a4 100644 --- a/frontend/src/MarketplacePage/index.jsx +++ b/frontend/src/MarketplacePage/index.jsx @@ -1,19 +1,34 @@ -import React from 'react'; +import React, { useContext } from 'react'; import Layout from '@/_ui/Layout'; -import { ListGroupItem } from './ListGroupItem'; import { InstalledPlugins } from './InstalledPlugins'; import { MarketplacePlugins } from './MarketplacePlugins'; -import { marketplaceService, pluginsService } from '@/_services'; +import { marketplaceService, pluginsService, authenticationService } from '@/_services'; import { toast } from 'react-hot-toast'; +import { useNavigate } from 'react-router-dom'; import config from 'config'; +import { BreadCrumbContext } from '@/App/App'; +import FolderList from '@/_ui/FolderList/FolderList'; const MarketplacePage = ({ darkMode, switchDarkMode }) => { const [active, setActive] = React.useState('installed'); const [marketplacePlugins, setMarketplacePlugins] = React.useState([]); const [installedPlugins, setInstalledPlugins] = React.useState([]); const [fetchingInstalledPlugins, setFetching] = React.useState(false); + const { updateSidebarNAV } = useContext(BreadCrumbContext); - const ENABLE_MARKETPLACE_DEV_MODE = config.ENABLE_MARKETPLACE_DEV_MODE === 'true'; + const { admin } = authenticationService.currentSessionValue; + const ENABLE_MARKETPLACE_DEV_MODE = config.ENABLE_MARKETPLACE_DEV_MODE == 'true'; + + const navigate = useNavigate(); + + React.useEffect(() => { + updateSidebarNAV(''); + + if (!admin) { + navigate('/'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [admin]); React.useEffect(() => { marketplaceService @@ -44,26 +59,37 @@ const MarketplacePage = ({ darkMode, switchDarkMode }) => { setInstalledPlugins(data); }; + const itemRender = (key) => { + switch (key) { + case 'Marketplace': + return 'marketplace'; + case 'Installed': + return 'installed'; + default: + break; + } + }; + return (
    -
    +
    -
    +
    Plugins
    - setActive('installed')} - text="Installed" - /> - setActive('marketplace')} - text="Marketplace" - /> + {['Installed', 'Marketplace'].map((item, index) => ( + setActive(itemRender(item))} + > + {item} + + ))}
    {active === 'installed' ? ( diff --git a/frontend/src/OrganizationSettingsPage/index.jsx b/frontend/src/OrganizationSettingsPage/index.jsx index fabd713719..df9df2fb5e 100644 --- a/frontend/src/OrganizationSettingsPage/index.jsx +++ b/frontend/src/OrganizationSettingsPage/index.jsx @@ -1,138 +1,80 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import cx from 'classnames'; -import { useTranslation } from 'react-i18next'; import Layout from '@/_ui/Layout'; import { ManageOrgUsers } from '@/ManageOrgUsers'; import { ManageGroupPermissions } from '@/ManageGroupPermissions'; import { ManageSSO } from '@/ManageSSO'; import { ManageOrgVars } from '@/ManageOrgVars'; import { authenticationService } from '@/_services'; +import { BreadCrumbContext } from '../App/App'; +import FolderList from '@/_ui/FolderList/FolderList'; +import { OrganizationList } from '../_components/OrganizationManager/List'; export function OrganizationSettings(props) { const [admin, setAdmin] = useState(authenticationService.currentSessionValue?.admin); - const [selectedTab, setSelectedTab] = useState(admin ? 'users' : 'manageEnvVars'); - const { t } = useTranslation(); + const [selectedTab, setSelectedTab] = useState(admin ? 'Users & permissions' : 'manageEnvVars'); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + + const sideBarNavs = ['Users', 'Groups', 'SSO', 'Workspace variables']; + const defaultOrgName = (groupName) => { + switch (groupName) { + case 'Users': + return 'Users & permissions'; + case 'Groups': + return 'manageGroups'; + case 'SSO': + return 'manageSSO'; + case 'Workspace variables': + return 'manageEnvVars'; + default: + return groupName; + } + }; useEffect(() => { const subscription = authenticationService.currentSession.subscribe((newOrd) => { setAdmin(newOrd?.admin); - admin ? setSelectedTab('users') : setSelectedTab('manageEnvVars'); + admin ? updateSidebarNAV('Users & permissions') : updateSidebarNAV('Workspace variables'); }); () => subscription.unsubsciption(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [authenticationService.currentSessionValue?.admin]); - const selectedClassName = props.darkMode ? 'bg-dark-indigo' : 'bg-light-indigo'; return (
    -
    -
    - {admin && ( - <> -
    +
    + {sideBarNavs.map((item, index) => { + return ( + <> + {(admin || item == 'Workspace variables') && ( + { + setSelectedTab(defaultOrgName(item)); + if (item == 'Users') updateSidebarNAV('Users & permissions'); + else updateSidebarNAV(item); + }} + selectedItem={selectedTab == defaultOrgName(item)} + dataCy={item.toLowerCase().replace(/\s+/g, '-')} + > + {item} + )} - onClick={() => setSelectedTab('users')} - data-cy="manage-users-option" - > - - - - -  {t('header.organization.menus.menusList.manageUsers', 'Users')} -
    -
    setSelectedTab('manageGroups')} - data-cy="manage-groups-option" - > - - - -  {t('header.organization.menus.menusList.manageGroups', 'Manage Groups')} -
    -
    setSelectedTab('manageSSO')} - data-cy="manage-sso-option" - > - - - -  {t('header.organization.menus.menusList.manageSso', 'SSO')} -
    - - )} -
    setSelectedTab('manageEnvVars')} - data-cy="workspace-variable-option" - > - - - -  {t('header.organization.menus.menusList.manageEnv', 'Manage Environment Variables')} -
    + + ); + })}
    +
    -
    -
    - {selectedTab === 'users' && } + +
    +
    + {selectedTab === 'Users & permissions' && } {selectedTab === 'manageGroups' && } {selectedTab === 'manageSSO' && } {selectedTab === 'manageEnvVars' && } diff --git a/frontend/src/ResetPassword/ResetPasswordPage.jsx b/frontend/src/ResetPassword/ResetPasswordPage.jsx index 00b26f6a45..a7b2dbdd97 100644 --- a/frontend/src/ResetPassword/ResetPasswordPage.jsx +++ b/frontend/src/ResetPassword/ResetPasswordPage.jsx @@ -129,7 +129,7 @@ class ResetPasswordComponent extends React.Component { )}
    - Password must be at least 5 characterss + Password must be at least 5 characters @@ -181,7 +181,7 @@ class ResetPasswordComponent extends React.Component { )}
    - Password must be at least 5 characterss + Password must be at least 5 characters diff --git a/frontend/src/SettingsPage/SettingsPage.jsx b/frontend/src/SettingsPage/SettingsPage.jsx index 5beef8a181..3d3a7bded1 100644 --- a/frontend/src/SettingsPage/SettingsPage.jsx +++ b/frontend/src/SettingsPage/SettingsPage.jsx @@ -1,13 +1,15 @@ -import React from 'react'; +import React, { useContext, useEffect } from 'react'; import { authenticationService, userService } from '@/_services'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import Layout from '@/_ui/Layout'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { BreadCrumbContext } from '@/App/App'; function SettingsPage(props) { const currentSession = authenticationService.currentSessionValue; + const email = currentSession?.current_user.email; const [firstName, setFirstName] = React.useState(currentSession?.current_user.first_name); - const [email, setEmail] = React.useState(currentSession?.current_user.email); const [lastName, setLastName] = React.useState(currentSession?.current_user.last_name); const [currentpassword, setCurrentPassword] = React.useState(''); const [newPassword, setNewPassword] = React.useState(''); @@ -17,6 +19,12 @@ function SettingsPage(props) { const [selectedFile, setSelectedFile] = React.useState(null); const focusRef = React.useRef(null); const { t } = useTranslation(); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + + useEffect(() => { + updateSidebarNAV(''); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const updateDetails = async () => { const firstNameMatch = firstName.match(/^ *$/); @@ -120,23 +128,10 @@ function SettingsPage(props) { return (
    -
    -
    -
    -
    -
    -
    -

    - {t('header.profileSettingPage.profileSettings', 'Profile Settings')} -

    -
    -
    -
    -
    - -
    +
    +
    -
    +

    {t('header.profileSettingPage.profile', 'Profile')} @@ -145,7 +140,7 @@ function SettingsPage(props) {
    -
    +
    @@ -162,7 +157,7 @@ function SettingsPage(props) {
    -
    +
    @@ -181,7 +176,7 @@ function SettingsPage(props) {
    -
    +
    @@ -198,10 +193,10 @@ function SettingsPage(props) {
    -
    -
    +
    +
    + { const file = e.target.files[0]; @@ -220,20 +215,16 @@ function SettingsPage(props) {
    - + {/* An !important style on theme.scss is making the last child of every .card-body color to #c3c3c3!. */} {/* The div below is a placeholder to prevent it from affecting the button above. */}

    -
    +

    {t('header.profileSettingPage.changePassword', 'Change password')} @@ -242,7 +233,7 @@ function SettingsPage(props) {
    -
    +
    @@ -258,7 +249,7 @@ function SettingsPage(props) {
    -
    +
    @@ -276,7 +267,7 @@ function SettingsPage(props) {
    -
    +
    @@ -293,14 +284,14 @@ function SettingsPage(props) { />
    - + {/* An !important style on theme.scss is making the last child of every .card-body color to #c3c3c3!. */} {/* The div below is a placeholder to prevent it from affecting the button above. */}
    diff --git a/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx b/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx index d7022c0c00..8bdca77660 100644 --- a/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx +++ b/frontend/src/TooljetDatabase/Drawers/CreateColumnDrawer/index.jsx @@ -1,31 +1,27 @@ -import React, { useState, useContext } from 'react'; +import React, { useContext } from 'react'; import Drawer from '@/_ui/Drawer'; import { toast } from 'react-hot-toast'; import CreateColumnForm from '../../Forms/ColumnForm'; import { TooljetDatabaseContext } from '../../index'; import { tooljetDatabaseService } from '@/_services'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; -const CreateColumnDrawer = () => { +const CreateColumnDrawer = ({ setIsCreateColumnDrawerOpen, isCreateColumnDrawerOpen }) => { const { organizationId, selectedTable, setColumns, setSelectedTableData } = useContext(TooljetDatabaseContext); - const [isCreateColumnDrawerOpen, setIsCreateColumnDrawerOpen] = useState(false); return ( <> + setIsCreateColumnDrawerOpen(false)} position="right"> { diff --git a/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx b/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx index b3670d0087..6d4ace041a 100644 --- a/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx +++ b/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx @@ -4,6 +4,7 @@ import { toast } from 'react-hot-toast'; import CreateRowForm from '../../Forms/RowForm'; import { TooljetDatabaseContext } from '../../index'; import { tooljetDatabaseService } from '@/_services'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const CreateRowDrawer = ({ isCreateRowDrawerOpen, setIsCreateRowDrawerOpen }) => { const { organizationId, selectedTable, setSelectedTableData, setTotalRecords } = useContext(TooljetDatabaseContext); @@ -11,22 +12,13 @@ const CreateRowDrawer = ({ isCreateRowDrawerOpen, setIsCreateRowDrawerOpen }) => return ( <> setIsCreateRowDrawerOpen(false)} position="right"> - +
    + setIsCreateTableDrawerOpen(!isCreateTableDrawerOpen)} + className="create-new-table-btn" + data-cy="add-table-button" + > + Create new table + +
    setIsCreateTableDrawerOpen(false)} position="right"> { @@ -39,6 +37,7 @@ export default function CreateTableDrawer() { if (Array.isArray(data?.result) && data.result.length > 0) { setTables(data.result || []); setSelectedTable(tableName); + updateSidebarNAV(tableName); } }); setIsCreateTableDrawerOpen(false); diff --git a/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx b/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx index 98ab2ffc2f..7a9f544ec6 100644 --- a/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx +++ b/frontend/src/TooljetDatabase/Drawers/EditRowDrawer/index.jsx @@ -12,8 +12,7 @@ const EditRowDrawer = ({ isCreateRowDrawerOpen, setIsCreateRowDrawerOpen }) => { <> diff --git a/frontend/src/TooljetDatabase/Filter/index.jsx b/frontend/src/TooljetDatabase/Filter/index.jsx index db41ba8f28..2708e3a4a9 100644 --- a/frontend/src/TooljetDatabase/Filter/index.jsx +++ b/frontend/src/TooljetDatabase/Filter/index.jsx @@ -6,6 +6,7 @@ import { FilterForm } from '../Forms/FilterForm'; import { isEmpty } from 'lodash'; import { pluralize } from '@/_helpers/utils'; import { useMounted } from '@/_hooks/use-mount'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery }) => { const [show, setShow] = useState(false); @@ -14,7 +15,7 @@ const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery const isMounted = useMounted(); const popover = ( - +
    {Object.values(filters).map((filter, index) => { @@ -84,15 +85,14 @@ const Filter = ({ filters, setFilters, handleBuildFilterQuery, resetFilterQuery placement="bottom" overlay={popover} > -
    */} -
    +
    -
    +
    Delete
    diff --git a/frontend/src/TooljetDatabase/Table/Footer.jsx b/frontend/src/TooljetDatabase/Table/Footer.jsx index 65d9b74fb6..471686a8d5 100644 --- a/frontend/src/TooljetDatabase/Table/Footer.jsx +++ b/frontend/src/TooljetDatabase/Table/Footer.jsx @@ -86,7 +86,7 @@ const Footer = ({ darkMode, openCreateRowDrawer, dataLoading, tableDataLength }) size="sm" styles={{ width: '118px', fontSize: '12px', fontWeight: 700, borderColor: darkMode && 'transparent' }} > - +
    {tableDataLength > 0 && ( @@ -101,10 +101,10 @@ const Footer = ({ darkMode, openCreateRowDrawer, dataLoading, tableDataLength }) isDisabled={dataLoading} />
    -
    +

    { > {headerGroups.map((headerGroup, index) => ( - + {headerGroup.headers.map((column, index) => ( { ))} ))} + 0 && !darkMode, @@ -266,29 +287,33 @@ const Table = ({ openCreateRowDrawer }) => { rows.map((row, index) => { prepareRow(row); return ( - - {row.cells.map((cell, index) => { - console.log('----qa-checking----', cell); - const dataCy = - cell.column.id === 'selection' - ? `${cell.row.values?.id}-checkbox` - : `id-${cell.row.values?.id}-column-${cell.column.id}`; - return ( - - ); - })} - + <> + + {row.cells.map((cell, index) => { + const dataCy = + cell.column.id === 'selection' + ? `${cell.row.values?.id}-checkbox` + : `id-${cell.row.values?.id}-column-${cell.column.id}`; + return ( + + ); + })} + + ); }) )} +
    {column.render('Header')} + + {column.Header == 'id' ? 'serial' : checkDataType(column?.dataType)} +
    - {isBoolean(cell?.value) ? cell?.value?.toString() : cell.render('Cell')} -
    + {isBoolean(cell?.value) ? cell?.value?.toString() : cell.render('Cell')} +
    diff --git a/frontend/src/TooljetDatabase/TableList/index.jsx b/frontend/src/TooljetDatabase/TableList/index.jsx index 72cd4724de..21ed8440f3 100644 --- a/frontend/src/TooljetDatabase/TableList/index.jsx +++ b/frontend/src/TooljetDatabase/TableList/index.jsx @@ -5,11 +5,17 @@ import { isEmpty } from 'lodash'; import { TooljetDatabaseContext } from '../index'; import { tooljetDatabaseService } from '@/_services'; import { ListItem } from '../TableListItem'; +import { BreadCrumbContext } from '../../App/App'; +import Search from '../Search'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const List = () => { const { organizationId, tables, searchParam, selectedTable, setTables, setSelectedTable } = useContext(TooljetDatabaseContext); const [loading, setLoading] = useState(false); + const [showInput, setShowInput] = useState(false); + const { updateSidebarNAV } = useContext(BreadCrumbContext); + const darkMode = localStorage.getItem('darkMode') === 'true'; async function fetchTables() { setLoading(true); @@ -24,6 +30,7 @@ const List = () => { if (Array.isArray(data?.result)) { setTables(data.result || []); setSelectedTable(data?.result[0]?.table_name); + updateSidebarNAV(data?.result[0]?.table_name); } } @@ -40,8 +47,33 @@ const List = () => { return ( <> -
    - All tables ({filteredTables.length}) +
    + {!showInput ? ( + <> + All tables ({filteredTables.length}) + +
    { + setShowInput(true); + }} + data-cy="create-new-folder-button" + > + +
    + + ) : ( + setShowInput(false)} + customClass="tj-common-search-input" + autoFocus={true} + /> + )}
    {loading && } @@ -54,6 +86,7 @@ const List = () => { onDeleteCallback={fetchTables} onClick={() => { setSelectedTable(table_name); + updateSidebarNAV(table_name); }} /> ))} diff --git a/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx b/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx index 229bb00f6b..a817c86a37 100644 --- a/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx +++ b/frontend/src/TooljetDatabase/TableListItem/ActionsPopover/index.jsx @@ -5,13 +5,13 @@ import Popover from 'react-bootstrap/Popover'; import EditIcon from './Icons/Edit.svg'; // import CloneIcon from './Icons/Clone.svg'; import DeleteIcon from './Icons/Delete.svg'; -import EllipsisIcon from './Icons/Ellipsis.svg'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; export const ListItemPopover = ({ onEdit, onDelete, darkMode }) => { const [open, setOpen] = React.useState(false); const popover = ( - +
    @@ -48,7 +48,7 @@ export const ListItemPopover = ({ onEdit, onDelete, darkMode }) => { return (
    { transition={false} > - +
    diff --git a/frontend/src/TooljetDatabase/TableListItem/index.jsx b/frontend/src/TooljetDatabase/TableListItem/index.jsx index 801d1b1182..886fa06b8e 100644 --- a/frontend/src/TooljetDatabase/TableListItem/index.jsx +++ b/frontend/src/TooljetDatabase/TableListItem/index.jsx @@ -15,6 +15,10 @@ export const ListItem = ({ active, onClick, text = '', onDeleteCallback }) => { const [isEditTableDrawerOpen, setIsEditTableDrawerOpen] = useState(false); const darkMode = localStorage.getItem('darkMode') === 'true'; + function updateSelectedTable(tablename) { + setSelectedTable(tablename); + } + const handleDeleteTable = async () => { const shouldDelete = confirm(`Are you sure you want to delete the table "${text}"?`); if (shouldDelete) { @@ -48,7 +52,10 @@ export const ListItem = ({ active, onClick, text = '', onDeleteCallback }) => { onClick={onClick} > - + {text} @@ -62,11 +69,11 @@ export const ListItem = ({ active, onClick, text = '', onDeleteCallback }) => { { tooljetDatabaseService.findAll(organizationId).then(({ data = [] }) => { if (Array.isArray(data?.result) && data.result.length > 0) { setTables(data.result || []); - setSelectedTable(data?.result[0]?.table_name); } }); setIsEditTableDrawerOpen(false); diff --git a/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx b/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx index c52fdb65b9..baeda46249 100644 --- a/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx +++ b/frontend/src/TooljetDatabase/TooljetDatabasePage/index.jsx @@ -24,9 +24,9 @@ const TooljetDatabasePage = ({ totalTables }) => { setSortFilters, } = useContext(TooljetDatabaseContext); - const darkMode = localStorage.getItem('darkMode') === 'true'; const [isCreateRowDrawerOpen, setIsCreateRowDrawerOpen] = useState(false); const [isEditRowDrawerOpen, setIsEditRowDrawerOpen] = useState(false); + const [isCreateColumnDrawerOpen, setIsCreateColumnDrawerOpen] = useState(false); const EmptyState = () => { return ( @@ -54,29 +54,20 @@ const TooljetDatabasePage = ({ totalTables }) => { return (
    -
    +
    {totalTables === 0 && } {selectedTable && ( <>
    -
    - - {selectedTable} - -
    -
    -
    +
    +
    - + {columns?.length > 0 && ( <> {
    - setIsCreateRowDrawerOpen(true)} /> +
    setIsCreateRowDrawerOpen(true)} + openCreateColumnDrawer={() => setIsCreateColumnDrawerOpen(true)} + /> )} diff --git a/frontend/src/_components/AppButton.jsx b/frontend/src/_components/AppButton.jsx index 83469d1b3a..fd4cacf982 100644 --- a/frontend/src/_components/AppButton.jsx +++ b/frontend/src/_components/AppButton.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import IconEl from '@/_ui/Icon/Icon'; export const ButtonBase = function ButtonBase(props) { const mapBaseSize = { @@ -12,6 +13,8 @@ export const ButtonBase = function ButtonBase(props) { as = 'button', // render it as a button or an anchor. children, disabled, + rightIcon, + leftIcon, ...restProps } = props; @@ -19,7 +22,9 @@ export const ButtonBase = function ButtonBase(props) { return ( + {leftIcon && leftIcon} {children} + {rightIcon && rightIcon} ); }; @@ -44,7 +49,7 @@ export const IconButton = function IconButton(props) { return ( - {Icon} + ); }; diff --git a/frontend/src/_components/ConfirmDialog.jsx b/frontend/src/_components/ConfirmDialog.jsx index 010c640efd..b5aec4ff30 100644 --- a/frontend/src/_components/ConfirmDialog.jsx +++ b/frontend/src/_components/ConfirmDialog.jsx @@ -1,10 +1,10 @@ import React, { useState, useEffect } from 'react'; -import cx from 'classnames'; import Modal from 'react-bootstrap/Modal'; import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; export function ConfirmDialog({ show, title, message, onConfirm, onCancel, confirmButtonLoading, darkMode }) { - darkMode = darkMode ?? (localStorage.getItem('darkMode') || false); + darkMode = darkMode ?? (localStorage.getItem('darkMode') === 'true' || false); const [showModal, setShow] = useState(show); const { t } = useTranslation(); @@ -28,7 +28,7 @@ export function ConfirmDialog({ show, title, message, onConfirm, onCancel, confi size="sm" animation={false} centered={true} - contentClassName={darkMode ? 'theme-dark' : ''} + contentClassName={`confirm-dialogue-modal ${darkMode ? 'dark-theme' : ''}`} data-cy="modal-component" > {title && ( @@ -52,18 +52,21 @@ export function ConfirmDialog({ show, title, message, onConfirm, onCancel, confi )} - {message} + + {message} + - - + ); diff --git a/frontend/src/_components/DynamicForm.jsx b/frontend/src/_components/DynamicForm.jsx index ef4397846e..1d00bfddc5 100644 --- a/frontend/src/_components/DynamicForm.jsx +++ b/frontend/src/_components/DynamicForm.jsx @@ -14,6 +14,7 @@ import Zendesk from '@/_components/Zendesk'; import ToolJetDbOperations from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations'; import { find, isEmpty } from 'lodash'; +import { ButtonSolid } from './AppButton'; const DynamicForm = ({ schema, @@ -124,6 +125,7 @@ const DynamicForm = ({ className, controller, }) => { + const source = schema?.source?.kind; const darkMode = localStorage.getItem('darkMode') === 'true'; if (!options) return; @@ -172,6 +174,7 @@ const DynamicForm = ({ } case 'react-component-oauth-authentication': return { + isGrpc: source === 'grpc', grant_type: options?.grant_type?.value, auth_type: options?.auth_type?.value, add_token_to: options?.add_token_to?.value, @@ -184,6 +187,8 @@ const DynamicForm = ({ scopes: options?.scopes?.value, username: options?.username?.value, password: options?.password?.value, + grpc_apiKey_key: options?.grpc_apikey_key?.value, + grpc_apiKey_value: options?.grpc_apikey_value?.value, bearer_token: options?.bearer_token?.value, auth_url: options?.auth_url?.value, auth_key: options?.auth_key?.value, @@ -308,14 +313,16 @@ const DynamicForm = ({ )} {(type === 'password' || encrypted) && selectedDataSource?.id && (
    - +
    )} {(type === 'password' || encrypted) && ( @@ -336,6 +343,7 @@ const DynamicForm = ({ {...getElementProps(obj[key])} {...computedProps[key]} data-cy={`${String(label).toLocaleLowerCase().replace(/\s+/g, '-')}-text-field`} + customWrap={true} //to be removed after whole ui is same /> ); diff --git a/frontend/src/_components/Menu.jsx b/frontend/src/_components/Menu.jsx index fa30938956..860cf03423 100644 --- a/frontend/src/_components/Menu.jsx +++ b/frontend/src/_components/Menu.jsx @@ -4,8 +4,8 @@ import Skeleton from 'react-loading-skeleton'; export function Menu({ isLoading, onChange, items, selected }) { return ( -
    -
      +
      +
        {!isLoading && Array.isArray(items) && items.length > 0 && @@ -14,7 +14,7 @@ export function Menu({ isLoading, onChange, items, selected }) { key={item.id} onClick={() => onChange(item.id)} className={cx({ - active: selected === item.id, + 'folder-list-selected': selected === item.id, })} > {item.label} diff --git a/frontend/src/_components/MultiSelect.jsx b/frontend/src/_components/MultiSelectUser.jsx similarity index 74% rename from frontend/src/_components/MultiSelect.jsx rename to frontend/src/_components/MultiSelectUser.jsx index bd63d8ee84..aec096d988 100644 --- a/frontend/src/_components/MultiSelect.jsx +++ b/frontend/src/_components/MultiSelectUser.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import Select from 'react-select-search'; import '@/_styles/widgets/multi-select.scss'; -function MultiSelect({ +function MultiSelectUser({ onSelect, onSearch, selectedValues = [], @@ -37,13 +37,37 @@ function MultiSelect({ [setSearchText, onSearch, selectedValues] ); + function renderCustom(props, option) { + return ( +
        +
        + { + onSelect([...selectedValues, option]); + }} + /> +
        +

        + {option?.first_name} {option?.last_name} +

        + {option?.email} +
        +
        +
        + {option?.first_name?.[0]} + {option?.last_name?.[0]} +
        +
        + ); + } + const filterOptions = useCallback( (options) => { return options?.filter((data) => !selectedValues.some((selected) => selected.value === data.value)); }, [selectedValues] ); - return (
        @@ -70,12 +94,14 @@ function MultiSelect({ } disabled={isLoading} fuzzySearch + renderOption={renderCustom} + customWrap={true} />
        ); } -MultiSelect.propTypes = { +MultiSelectUser.propTypes = { onSelect: PropTypes.func.isRequired, onReset: PropTypes.func, onSearch: PropTypes.func, @@ -86,4 +112,4 @@ MultiSelect.propTypes = { searchLabel: PropTypes.string, }; -export { MultiSelect }; +export { MultiSelectUser }; diff --git a/frontend/src/_components/NotificationCenter/index.jsx b/frontend/src/_components/NotificationCenter/index.jsx index 7203e1b86e..fc5e720953 100644 --- a/frontend/src/_components/NotificationCenter/index.jsx +++ b/frontend/src/_components/NotificationCenter/index.jsx @@ -6,11 +6,13 @@ import Spinner from '@/_ui/Spinner'; import { useTranslation } from 'react-i18next'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import { ToolTip } from '@/_components/ToolTip'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; export const NotificationCenter = ({ darkMode }) => { const [loading, setLoading] = React.useState(false); const [isRead, setIsRead] = React.useState(false); const [commentNotifications, setCommentNotifications] = React.useState([]); + const { t } = useTranslation(); async function fetchData() { setLoading(true); @@ -42,11 +44,11 @@ export const NotificationCenter = ({ darkMode }) => { const overlay = (
        -
        +

        {t('header.notificationCenter.notifications', 'Notifications')} @@ -112,27 +114,13 @@ export const NotificationCenter = ({ darkMode }) => { return ( -
        +
        - - - +
        + + {commentNotifications?.length !== 0 && } +
        - {commentNotifications?.length !== 0 && }
        ); diff --git a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx index a73f4afeb9..e59ac47150 100644 --- a/frontend/src/_components/OrganizationManager/CreateOrganization.jsx +++ b/frontend/src/_components/OrganizationManager/CreateOrganization.jsx @@ -3,6 +3,7 @@ import { organizationService } from '@/_services'; import AlertDialog from '@/_ui/AlertDialog'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import { appendWorkspaceId } from '../../_helpers/utils'; export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { @@ -42,7 +43,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { title={t('header.organization.createWorkspace', 'Create workspace')} >
        -
        +
        setNewOrgName(e.target.value)} @@ -51,22 +52,23 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => { disabled={isCreating} maxLength={25} data-cy="workspace-name-input-field" + autoFocus />
        -
        - - +
        diff --git a/frontend/src/_components/OrganizationManager/CustomSelect.jsx b/frontend/src/_components/OrganizationManager/CustomSelect.jsx index 4a82f16049..9056d939d7 100644 --- a/frontend/src/_components/OrganizationManager/CustomSelect.jsx +++ b/frontend/src/_components/OrganizationManager/CustomSelect.jsx @@ -5,60 +5,45 @@ import { EditOrganization } from './EditOrganization'; import { CreateOrganization } from './CreateOrganization'; import { useTranslation } from 'react-i18next'; import { authenticationService } from '@/_services'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; const Menu = (props) => { const { t } = useTranslation(); const { admin } = authenticationService.currentSessionValue; - + const darkMode = localStorage.getItem('darkMode') === 'true'; return ( -
        +
        {admin && ( <> -
        props.selectProps.setShowEditOrg(true)}> +
        props.selectProps.setShowEditOrg(true)} + >
        {props?.selectProps?.value?.label}
        -
        - - - - +
        +
        -
        )} -
        {props.children}
        +
        {props.children}
        - - - - - - {t('header.organization.addNewWorkSpace', 'Add new workspace')} - +
        + +
        + +
        + +
        + {t('header.organization.addNewWorkSpace', 'Add new workspace')}
        @@ -68,7 +53,9 @@ const Menu = (props) => { const SingleValue = ({ selectProps }) => { return (
        -
        {selectProps.value.name}
        +
        + {selectProps.value.name} +
        ); }; @@ -76,6 +63,7 @@ const SingleValue = ({ selectProps }) => { export const CustomSelect = ({ ...props }) => { const [showEditOrg, setShowEditOrg] = useState(false); const [showCreateOrg, setShowCreateOrg] = useState(false); + const darkMode = localStorage.getItem('darkMode') === 'true'; return ( <> @@ -83,13 +71,15 @@ export const CustomSelect = ({ ...props }) => { setNewOrgName(e.target.value)} @@ -45,17 +47,18 @@ export const EditOrganization = ({ showEditOrg, setShowEditOrg }) => { disabled={isCreating} value={newOrgName} maxLength={25} + autoFocus />
        -
        - - +
        diff --git a/frontend/src/_components/OrganizationManager/List.jsx b/frontend/src/_components/OrganizationManager/List.jsx index 282000e7b6..072850b68a 100644 --- a/frontend/src/_components/OrganizationManager/List.jsx +++ b/frontend/src/_components/OrganizationManager/List.jsx @@ -7,6 +7,7 @@ export const OrganizationList = function () { const { current_organization_id } = authenticationService.currentSessionValue; const [organizationList, setOrganizationList] = useState([]); const [getOrgStatus, setGetOrgStatus] = useState(''); + const darkMode = localStorage.getItem('darkMode') === 'true'; useEffect(() => { setGetOrgStatus('loading'); @@ -30,25 +31,29 @@ export const OrganizationList = function () { value: org.id, name: org.name, label: ( -
        -
        - - {getAvatar(org.name)} - +
        +
        + {getAvatar(org.name)}
        -
        -
        {org.name}
        +
        + {org.name}
        ), })); return ( - switchOrganization(id)} - /> +
        + switchOrganization(id)} + className={`tj-org-select ${darkMode && 'dark-theme'}`} + /> +
        ); }; diff --git a/frontend/src/_components/Pagination.jsx b/frontend/src/_components/Pagination.jsx index a500e513a3..7dc3c40169 100644 --- a/frontend/src/_components/Pagination.jsx +++ b/frontend/src/_components/Pagination.jsx @@ -56,11 +56,15 @@ export const Pagination = function Pagination({ currentPage, count, pageChanged, } return ( -
        +

        {t('homePage.pagination.showing', 'Showing')} {startingAppCount()}{' '} {t('homePage.pagination.to', 'to')} {endingAppCount()} {t('homePage.pagination.of', 'of')}{' '} - {count} + {count}

        • diff --git a/frontend/src/_components/Profile.jsx b/frontend/src/_components/Profile.jsx index 6bc11ba415..8163c925c6 100644 --- a/frontend/src/_components/Profile.jsx +++ b/frontend/src/_components/Profile.jsx @@ -6,8 +6,9 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import { useTranslation } from 'react-i18next'; import { ToolTip } from '@/_components/ToolTip'; import { getPrivateRoute } from '@/_helpers/routes'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; -export const Profile = function Header({ switchDarkMode, darkMode }) { +export const Profile = function Header({ darkMode }) { const currentSession = authenticationService.currentSessionValue; const [currentUser, setCurrentUser] = React.useState({ first_name: currentSession?.current_user.first_name, @@ -40,84 +41,28 @@ export const Profile = function Header({ switchDarkMode, darkMode }) { const getOverlay = () => { return ( -
          +
          - - - - + - {t('header.profile', 'Profile')} + {t('header.profile', 'Profile')} -
          switchDarkMode(!darkMode)} - data-cy="mode-switch-button" - > - - - - {darkMode ? 'Light Mode' : 'Dark Mode'} -
          - - - + - {t('header.logout', 'Logout')} + {t('header.logout', 'Logout')}
          ); diff --git a/frontend/src/_components/SearchBox.jsx b/frontend/src/_components/SearchBox.jsx index c539f46330..3c785f1010 100644 --- a/frontend/src/_components/SearchBox.jsx +++ b/frontend/src/_components/SearchBox.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import useDebounce from '@/_hooks/useDebounce'; import { useMounted } from '@/_hooks/use-mount'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; export function SearchBox({ width = '200px', @@ -13,6 +14,9 @@ export function SearchBox({ placeholder = 'Search', customClass = '', dataCy = '', + callBack, + onClearCallback, + autoFocus = false, }) { const [searchText, setSearchText] = useState(''); const debouncedSearchTerm = useDebounce(searchText, debounceDelay); @@ -20,34 +24,29 @@ export function SearchBox({ const handleChange = (e) => { setSearchText(e.target.value); + callBack?.(e); }; const clearSearchText = () => { setSearchText(''); + onClearCallback?.(); }; const mounted = useMounted(); useEffect(() => { if (mounted) { - onSubmit(debouncedSearchTerm); + onSubmit?.(debouncedSearchTerm); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedSearchTerm, onSubmit]); return (
          -
          +
          {!isFocused && ( - - - + )} setFocussed(true)} onBlur={() => setFocussed(false)} data-cy={`${dataCy}-search-bar`} + autoFocus={autoFocus} /> - {isFocused && searchText && ( - -
          - - - - - + {isFocused && ( + +
          +
          )} diff --git a/frontend/src/_components/index.js b/frontend/src/_components/index.js index 61c5573306..9eee8a94bb 100644 --- a/frontend/src/_components/index.js +++ b/frontend/src/_components/index.js @@ -9,4 +9,4 @@ export * from './Menu'; export * from './LoginLoader'; export * from './RedirectLoader'; export * from './FilterPreview'; -export * from './MultiSelect'; +export * from './MultiSelectUser'; diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index fbb9e26ed3..2629f15423 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -151,7 +151,7 @@ async function executeRunPycode(_ref, code, query, editorState, isPreview, mode) }; } - return pyodide.isPyProxy(result) ? result.toJs() : result; + return pyodide.isPyProxy(result) ? convertMapSet(result.toJs()) : result; }; return { data: await evaluatePythonCode(pyodide, code) }; @@ -344,8 +344,7 @@ function showModal(_ref, modal, show) { function logoutAction(_ref) { localStorage.clear(); - _ref.props.navigate('/login'); - window.location.href = '/login'; + authenticationService.logout(true); return Promise.resolve(); } @@ -751,6 +750,7 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') { 'onRowHovered', 'onSubmit', 'onInvalid', + 'onNewRowsAdded', ].includes(eventName) ) { const { component } = options; @@ -1582,3 +1582,17 @@ const getSelectedText = () => { navigator.clipboard.writeText(window.document.selection.createRange().text); } }; + +function convertMapSet(obj) { + if (obj instanceof Map) { + return Object.fromEntries(Array.from(obj, ([key, value]) => [key, convertMapSet(value)])); + } else if (obj instanceof Set) { + return Array.from(obj).map(convertMapSet); + } else if (Array.isArray(obj)) { + return obj.map(convertMapSet); + } else if (obj !== null && typeof obj === 'object') { + return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, convertMapSet(value)])); + } else { + return obj; + } +} diff --git a/frontend/src/_helpers/createWalkThrough.js b/frontend/src/_helpers/createWalkThrough.js index 8d7f2ee3cd..0d220fd8d0 100644 --- a/frontend/src/_helpers/createWalkThrough.js +++ b/frontend/src/_helpers/createWalkThrough.js @@ -25,8 +25,8 @@ export const initEditorWalkThrough = () => { { element: '.component-image-holder', popover: { - title: 'Drag and drop widgets', - description: 'From the widget sidebar, drag and drop widgets to the canvas.', + title: 'Drag and drop components', + description: 'From the component sidebar, drag and drop components to the canvas.', position: 'left', closeBtnText: 'Skip (1/6)', }, diff --git a/frontend/src/_helpers/routes.js b/frontend/src/_helpers/routes.js index 189335388f..28bfbb0825 100644 --- a/frontend/src/_helpers/routes.js +++ b/frontend/src/_helpers/routes.js @@ -15,7 +15,7 @@ export const getPrivateRoute = (page, params = {}) => { }; let url = routes[page]; - const urlParams = url.split('/').map((path) => { + const urlParams = url?.split('/').map((path) => { if (path.startsWith(':')) { return params[path.substring(1)]; } diff --git a/frontend/src/_services/authentication.service.js b/frontend/src/_services/authentication.service.js index cae8da7efe..0768f02d55 100644 --- a/frontend/src/_services/authentication.service.js +++ b/frontend/src/_services/authentication.service.js @@ -231,7 +231,7 @@ function resetPassword(params) { return fetch(`${config.apiUrl}/reset-password`, requestOptions).then(handleResponse); } -function logout() { +function logout(avoidRedirection = false) { const requestOptions = { method: 'GET', headers: authHeader(), @@ -242,16 +242,20 @@ function logout() { .then(handleResponseWithoutValidation) .then(() => { const loginPath = (window.public_config?.SUB_PATH || '/') + 'login'; - const pathname = window.public_config?.SUB_PATH - ? window.location.pathname.replace(window.public_config?.SUB_PATH, '') - : window.location.pathname; - window.location.href = - loginPath + - `?redirectTo=${ - !pathname.includes('integrations') - ? excludeWorkspaceIdFromURL(pathname) - : `${pathname.indexOf('/') === 0 ? '' : '/'}${pathname}` - }`; + if (avoidRedirection) { + window.location.href = loginPath; + } else { + const pathname = window.public_config?.SUB_PATH + ? window.location.pathname.replace(window.public_config?.SUB_PATH, '') + : window.location.pathname; + window.location.href = + loginPath + + `?redirectTo=${ + !pathname.includes('integrations') + ? excludeWorkspaceIdFromURL(pathname) + : `${pathname.indexOf('/') === 0 ? '' : '/'}${pathname}` + }`; + } }) .catch(() => { authenticationService.updateCurrentSession({ diff --git a/frontend/src/_services/organization.service.js b/frontend/src/_services/organization.service.js index 05c89c117b..e15cc2de37 100644 --- a/frontend/src/_services/organization.service.js +++ b/frontend/src/_services/organization.service.js @@ -15,8 +15,8 @@ export const organizationService = { function getUsers(page, options) { const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; - const { firstName, lastName, email, status } = options; - const query = queryString.stringify({ page, firstName, lastName, email, status }); + const { firstName, lastName, email, searchText, status } = options; + const query = queryString.stringify({ page, firstName, lastName, email, status, searchText }); return fetch(`${config.apiUrl}/organizations/users?${query}`, requestOptions).then(handleResponse); } diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 50b6934860..78d088b326 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -15,8 +15,9 @@ $btn-dark-color: #FFFFFF; font-weight: 500; cursor: pointer; transition: all 0.3s ease-in-out; + &:hover { - @if $bg != none { + @if $bg !=none { background-color: darken($bg, 10%); } } @@ -24,166 +25,171 @@ $btn-dark-color: #FFFFFF; .base-button { - @include button($btn-bg, $btn-color); - border-radius: $base-border-radius; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 4px 16px; - border: 1px solid #D7DBDF; + @include button($btn-bg, $btn-color); + border-radius: $base-border-radius; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px 16px; + border: 1px solid #D7DBDF; } .base-button.dark { - background: $btn-dark-bg; - color: $btn-dark-color; - border-color: #11181C; + background: $btn-dark-bg; + color: $btn-dark-color; + border-color: #11181C; - &:hover { - background: lighten($btn-dark-bg, 10%); - } + &:hover { + background: lighten($btn-dark-bg, 10%); + } - img { - filter: brightness(0) invert(1); - } + img { + filter: brightness(0) invert(1); + } } -.unstyled-button { - @include button(none, inherit); - border: none; - font-size: 12px; - font-family: 'Roboto'; - font-style: normal; - font-weight: 400; - line-height: 20px; +.unstyled-button { + @include button(none, inherit); + border: none; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; } .unstyled-button.dark { - color: #FFFFFF; + color: #FFFFFF; - img { - filter: brightness(0) invert(1); - } + img { + filter: brightness(0) invert(1); + } } .page-handle-button-container { - border-radius: $base-border-radius; - display: flex; - flex-direction: row; - justify-content: left; - align-items: center; - padding: 6px 8px; - border: 1px solid #D7DBDF; - height: 32px; + border-radius: $base-border-radius; + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + padding: 6px 8px; + border: 1px solid #D7DBDF; + height: 32px; - img { - position: absolute; - right: 0 !important; - margin-right: 1.5rem !important; - filter: invert(38%) sepia(85%) saturate(5221%) hue-rotate(217deg) brightness(91%) contrast(90%); - } + img { + position: absolute; + right: 0 !important; + margin-right: 1.5rem !important; + filter: invert(38%) sepia(85%) saturate(5221%) hue-rotate(217deg) brightness(91%) contrast(90%); + } } .popover-dark-themed .page-handle-button-container { - border-color: #697177; - img { - filter: invert(95%) sepia(38%) saturate(4716%) hue-rotate(180deg) brightness(113%) contrast(102%); - } + border-color: #697177; + + img { + filter: invert(95%) sepia(38%) saturate(4716%) hue-rotate(180deg) brightness(113%) contrast(102%); + } } .leftsidebar-panel-header { - height: 100%; - background-color: #F1F3F5; - - .panel-header-container { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - height: 52px; - border-bottom: 1px solid #E6E8EB; - - .add-new-page { - margin-left: 4px; - } - - } - .panel-search-container { - padding: 8px 12px; - border-bottom: 1px solid #E6E8EB; + height: 100%; + background-color: #F1F3F5; + + .panel-header-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + height: 52px; + border-bottom: 1px solid #E6E8EB; + + .add-new-page { + margin-left: 4px; } + } + .panel-search-container { + padding: 8px 12px; + border-bottom: 1px solid #E6E8EB; + } +} + .leftsidebar-panel-header.dark { - // background-color: #202425; - background-color: #1F2936; - .panel-header-container, .panel-search-container { - border-color: #697177; - } + background-color: #1F2936 !important; + + .panel-header-container, + .panel-search-container { + border-color: #697177; + } } .page-selector-panel-body { - height: 100%; - padding: 12px; - background-color: #FFFFFF; + height: 100%; + padding: 12px; + background-color: #FFFFFF; - .page-handler { - height: 32px !important; - padding: 0; - margin-bottom: 6px; - font-weight: 500; - - .card, .card-body { - padding: 2px; - height: 32px; + .page-handler { + height: 32px !important; + padding: 0; + margin-bottom: 6px; + font-weight: 500; - .page-name { - padding: 2px; - } - } - - .card.active { - background: #E6EDFE; - } - - .card.non-active-page { - border: none; - box-shadow: none; - } - - .card:hover { - background: #ECEEF0; - } - - .page-name-input { - height: 32px; - } + .card, + .card-body { + padding: 2px; + height: 32px; + + .page-name { + padding: 2px; } + } + + .card.active { + background: #E6EDFE; + } + + .card.non-active-page { + border: none; + box-shadow: none; + } + + .card:hover { + background: #ECEEF0; + } + + .page-name-input { + height: 32px; + } + } } .page-selector-panel-body.dark { - background: #1F2936; + background: #1F2936 !important; - .page-handler { - .card { - background: none !important - } - .card.active { - background: #26292B !important; - } - .card:hover{ - background: #2C3547 !important; - } + .page-handler { + .card { + background: none !important } + + .card.active { + background: #26292B !important; + } + + .card:hover { + background: #2C3547 !important; + } + } } .left-sidebar-page-selector.dark { - background: #1F2936 !important; + background: #1F2936 !important; - .clear-icon { - filter: invert(100%); - } + .clear-icon { + filter: invert(100%); + } } #page-handler-menu.global-settings { @@ -192,34 +198,34 @@ $btn-dark-color: #FFFFFF; } #page-handler-menu { - border-radius: 4px; - width: 238px; - margin-top: 0.2rem !important; - margin-left: 0.5rem !important; - box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25); - - .popover-body { - padding: 16px 6px 0px 6px; - height: 100%; - - .card-body { - padding: 0; - height: 100%; - } - - .field { - font-weight: 500; - font-size: 0.7rem; + border-radius: 4px; + width: 238px; + margin-top: 0.2rem !important; + margin-left: 0.5rem !important; + box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25); - &:hover { - color:#919eab; - } - - &__danger { - color: #ff6666; - } + .popover-body { + padding: 16px 6px 0px 6px; + height: 100%; + + .card-body { + padding: 0; + height: 100%; + } + + .field { + font-weight: 500; + font-size: 0.7rem; + + &:hover { + color: #919eab; + } + + &__danger { + color: #ff6666; } } + } } .page-icons { @@ -232,51 +238,52 @@ $btn-dark-color: #FFFFFF; } .page-handler-alert { - background-color: #fff5f0!important; - border: 1px solid #FFF1E7!important; + background-color: #fff5f0 !important; + border: 1px solid #FFF1E7 !important; } .page-handle-edit-container { - height: 60px; - width: 100%; + height: 60px; + width: 100%; - .input-group { - height: 42px; - padding: 2px; - } - - .input-group-text { - border: none; - background: none; - font-weight: 400; - font-size: 14px; - line-height: 20px; - padding-right: 4px; - } + .input-group { + height: 42px; + padding: 2px; + } - .page-handler-input { - border-radius: $base-border-radius !important; - } + .input-group-text { + border: none; + background: none; + font-weight: 400; + font-size: 14px; + line-height: 20px; + padding-right: 4px; + } + + .page-handler-input { + border-radius: $base-border-radius !important; + } } .page-handle-edit-modal.theme-dark { - background: none !important; + background: none !important; - .input-group-text { - border: none !important; - background: none!important; ; - } + .input-group-text { + border: none !important; + background: none !important; + ; + } } .page-handle-tip { - text-decoration: none!important; + text-decoration: none !important; } .delete-btn.field__danger { - img { - filter: invert(37%) sepia(50%) saturate(2105%) hue-rotate(342deg) brightness(93%) contrast(93%); - } + img { + filter: invert(37%) sepia(50%) saturate(2105%) hue-rotate(342deg) brightness(93%) contrast(93%); + } } .clear-icon { diff --git a/frontend/src/_styles/designtheme.scss b/frontend/src/_styles/designtheme.scss index 99b9c3fd5b..598722b118 100644 --- a/frontend/src/_styles/designtheme.scss +++ b/frontend/src/_styles/designtheme.scss @@ -63,9 +63,11 @@ :root { --base: white; + --base-black: #121212; } .dark-theme { /* Remap your colors for dark mode */ --base: #121212; + --base-black: white; } \ No newline at end of file diff --git a/frontend/src/_styles/drawer.scss b/frontend/src/_styles/drawer.scss index fad7a767e7..7640377da6 100644 --- a/frontend/src/_styles/drawer.scss +++ b/frontend/src/_styles/drawer.scss @@ -3,14 +3,16 @@ } .drawer { - background: #fff; - width: 40%; + background: var(--base); + width: 534px; height: 100%; overflow: auto; position: fixed; - box-shadow: 0 0 15px rgba(0, 0, 0, 0.5); + border: 1px solid var(--slate5); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); transition: transform var(--transition-speed) ease; z-index: 1000; + background: var(--base); &.left { top: 0; @@ -44,11 +46,14 @@ } .drawer-container.in.open { - .left, .right { + + .left, + .right { transform: translateX(0); } - .top, .bottom { + .top, + .bottom { transform: translateY(0); } } @@ -73,14 +78,3 @@ pointer-events: auto; z-index: 999; } - -.theme-dark { - .drawer { - background: #22272E; - - .btn { - background-color: #273342; - color: #fff; - } - } -} \ No newline at end of file diff --git a/frontend/src/_styles/dropdown-custom.scss b/frontend/src/_styles/dropdown-custom.scss new file mode 100644 index 0000000000..ad6690642d --- /dev/null +++ b/frontend/src/_styles/dropdown-custom.scss @@ -0,0 +1,71 @@ +// for selects and dropdowns across app dashboard +.react-select__control { + background-color: var(--base) !important; +} + +.react-select__menu { + background-color: var(--base) !important; + border: 1px solid var(--slate3) !important; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important; + margin: 0px !important; + + .react-select__menu-list { + background-color: var(--base) !important; + overflow-y: auto; + + .react-select__option { + background-color: var(--base) !important; + + &:hover { + background-color: var(--slate3) !important; + } + } + } +} + +.org-select-container { + height: 52px; + display: flex; + align-items: center; + justify-content: center; + border-top: 1px solid var(--slate3); +} + +.tj-org-select { + .react-select__control { + width: 262px; + height: 32px; + + &:hover { + background: var(--slate2) !important; + } + + &:active { + background: var(--slate3) !important; + } + } +} + +.users-filter-dropdown, +.data-type-dropdown-section, +.row-edit-select-container, +.select-operation-field, +.select-column-field, +.select-order-field, +.select-column-field, +.records-dropdown-field { + .react-select__control { + border: 1px solid var(--slate7) !important; + } +} + +.css-1ms6gku-MenuPortal, +.css-169zxdi-MenuList { + .react-select__option { + border-radius: 6px; + } +} + +.css-nw08ma-menu { + box-shadow: none !important; +} \ No newline at end of file diff --git a/frontend/src/_styles/global-datasources.scss b/frontend/src/_styles/global-datasources.scss index 6040a4a1a6..3dfc390820 100644 --- a/frontend/src/_styles/global-datasources.scss +++ b/frontend/src/_styles/global-datasources.scss @@ -2,26 +2,47 @@ @import "./designtheme.scss"; .global-datasources-sidebar { - height: calc(100vh - 48px); + height: calc(100vh - 64px); max-width: 288px; - overflow-y: scroll; + overflow-y: auto; + background: var(--base); .add-datasource-btn { - height: 32px; + height: 40px; background: var(--indigo9); + width: 248px !important; + display: flex; + margin: 0 auto; + border-radius: 6px; } .datasources-list { display: flex; + padding: 6px 8px; + width: 248px; + height: 32px; + margin-bottom: 2px; + + &:focus-visible { + box-shadow: 0px 0px 0px 4px #DFE3E6; + outline: none; + } &:hover { - background-color: var(--indigo2); + background: var(--slate4); + border-radius: 6px; .ds-delete-btn { display: block; } } + &:active { + background: var(--indigo4); + box-shadow: none; + } + + .ds-delete-btn { display: none; border: none; @@ -66,26 +87,40 @@ .datasource-modal-container { position: relative; + background: var(--slate2); + + .modal-header { + background-color: var(--base) !important; + } .modal { position: absolute; + background: var(--slate2); } .modal-content { border: 1px solid var(--slate5); + background-color: var(--base) !important; + .input-icon { &:hover { input { padding-right: 2rem !important; } + .input-icon-addon { justify-content: flex-end; } } } + .close-btn.dark { filter: none !important; background-color: revert !important; } } } + +.datasource-inner-sidebar-wrap { + min-height: calc(100vh - 185px); + } \ No newline at end of file diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss index 6da070360a..1819eedaff 100644 --- a/frontend/src/_styles/left-sidebar.scss +++ b/frontend/src/_styles/left-sidebar.scss @@ -1,11 +1,12 @@ @import "./colors.scss"; +@import "./designtheme.scss"; .left-sidebar { - background: $white; + background: var(--base) !important; .sidebar-svg-icon { &:hover { - background: #ECEEF0; + background: var(--slate5); border-radius: 6px; } } @@ -22,12 +23,14 @@ } } } + .left-sidebar-stack-bottom { width: 48px; position: fixed; bottom: 5px; text-align: center; } + .popover { position: fixed; left: 48px; @@ -35,18 +38,22 @@ overflow: auto; max-height: 60%; } + .datasources-popover { top: 160px; width: 200px; + .add-btn { border: none; margin-top: -4px; } } + .debugger-popover { top: 220px; cursor: pointer; } + .global-settings-popover { top: 260px; max-width: 350px; @@ -58,6 +65,7 @@ overflow: hidden; transition: max-height 0.25s cubic-bezier(0.5, 0, 0.1, 1); } + &.open { transition: max-height 0.25s cubic-bezier(0.5, 0, 0.1, 1); overflow: hidden; @@ -67,21 +75,26 @@ .object-key-val { margin-top: 0; } - .icon-container + span { + + .icon-container+span { margin-top: -0.14rem; - + span { + + +span { transform: translateY(-0.2rem); } } - .brace-row > span { + + .brace-row>span { transform: translateY(-0.2rem); } } } + .iopen { transform: rotate(90deg); transition: 0.12s; } + .debugger-badge { position: fixed; margin-top: -0.5rem; @@ -90,30 +103,37 @@ width: 20px; font-weight: 400; } + .zoom-popover { top: 500px; } + .sidebar-zoom { white-space: nowrap; font-size: 12px; } + .show { display: block; } + .hide { display: none; } + .dark-mode { transform: scale(0.7); } + .no-border { border: 0; } + .comment-badge { position: absolute; top: 5px; right: 5px; - transform: translate(50%,-50%); + transform: translate(50%, -50%); transform-origin: 100% 0%; } } @@ -125,6 +145,7 @@ border-radius: 4px; color: $primary; height: 36px; + &:hover { background: $primary; color: #fffffc; @@ -134,17 +155,19 @@ .dark-button { @extend .datasource-modal-button; background: transparent !important; + &:hover { - background: $primary !important; + background: $primary !important; color: #fffffc !important; } } .datasource-footer-info { - border: 1px solid #a6b6cc !important; box-sizing: border-box; - border-radius: 4px !important; - height: 68px; + height: 88px; + background: var(--slate2); + border: 1px solid var(--slate3); + border-radius: 6px; .copied { color: #7a95fb; @@ -155,6 +178,7 @@ button.copy-button { @extend .datasource-modal-button; width: 88px; + svg { margin-right: 0.5rem; } @@ -173,18 +197,30 @@ padding: 9px 12px !important; height: 34px; + &:focus-visible { + box-shadow: 0px 0px 0px 4px #DFE3E6 !important; + outline: none; + } + &:hover { - border-radius: 4px !important; - background-color: $bg-light; + background: var(--slate4) !important; + border-radius: 6px; + } + + &:active { + background: var(--indigo4) !important; + box-shadow: none; + } } .list-group-item.active { border-radius: 4px !important; - background-color: $primary !important; + background-color: var(--indigo4) !important; + color: var(--slate12); } - .list-group-item + .list-group-item.active { + .list-group-item+.list-group-item.active { margin-top: 0px !important; } } @@ -204,6 +240,7 @@ .selected-datasource-list-content { padding-left: 2rem !important; height: inherit; + .tab-content { width: 860px !important; position: absolute; @@ -222,6 +259,7 @@ svg { margin-top: 24px; } + span { margin: 12px 0 24px !important; } @@ -251,7 +289,7 @@ .modal-body-content.dark { .selected-datasource-list-content { .card:hover { - background-color: #2c405c !important; + background-color: var(--slate2) !important; } } } @@ -301,12 +339,13 @@ } .datasource-modal-sidebar-footer { - background: #283444; + background: var(--base); border-left: none; border-bottom: none; - border-color: #324156; + border-color: var(--slate5) !important; } } + .select-datasource-list-modal { .modal-body { padding: 0px !important; @@ -315,17 +354,18 @@ .modal-body { .datasource-modal-sidebar-footer { - border: 1px solid #d2ddec; + border: 1px solid var(--slate5); width: 242px !important; height: 71px; - background-color: #fffffc; - padding: 16px 24px !important; + background-color: var(--base); + padding: 16px 12px !important; position: absolute; left: 0; bottom: 0; p { margin: 0; + .footer-text { font-weight: 300; font-size: 12px; @@ -385,7 +425,8 @@ cursor: pointer; } -.close-btn.dark,.back-btn.dark { +.close-btn.dark, +.back-btn.dark { background-color: revert !important; } @@ -396,6 +437,7 @@ font-size: 18px; line-height: 22px; } + .suggestingDatasourcesWrapper { position: absolute; left: 0; @@ -410,14 +452,15 @@ .viewer-page-handler { height: 32px; padding: 0; - // max-width: 185px; - + .card { background: none; border: none; box-shadow: none; } - .card,.card-body { + + .card, + .card-body { padding: 3px; height: 32px; } @@ -447,7 +490,7 @@ .card.active { width: inherit; - background-color: #ECEEF0; + background-color: var(--slate5); color: #3e525b; } } @@ -462,6 +505,7 @@ .viewer-footer { height: 48px; } + .sidebar-comments { position: fixed !important; top: 0; @@ -469,14 +513,17 @@ border-left: 1px solid #d2ddec; padding: 5px 0 5px 5px !important; } + .sidebar-comments.dark { border-color: #324156; } + .sidebar-global-settings { position: absolute; left: 50px; top: 8px; } + .sidebar-h-100-popover { position: relative; height: 100vh; @@ -487,15 +534,104 @@ bottom: 3rem; } } + .sidebar-h-100-popover-inspector { min-width: 422px; } -.theme-dark{ - .left-sidebar{ + +.theme-dark { + .left-sidebar { .sidebar-svg-icon { &:hover { background: #2F3C4C; } } - } + } +} + +.left-sidebar { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 16px; + width: 56px; + border-right: 1px solid var(--slate5); +} + +.tj-leftsidebar-icon-wrap { + display: flex; + width: 44px; + max-height: 236px; + flex-direction: column; + align-items: center; +} + + +.tj-leftsidebar-icon-items-bottom { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + position: absolute; + bottom: 0; + padding-bottom: 8px; + width: 44px; + max-height: 180px; +} + +.tj-leftsidebar-icon-items { + margin-bottom: 22px; + height: 32px; + width: 32px; + border-radius: 4px; + display: flex; + justify-content: center; + align-items: center; + + + &:hover { + background: var(--slate4); + } + + &:focus-visible { + box-shadow: 0px 0px 0px 4px #DFE3E6; + background: var(--slate4); + outline: none; + } + + + &:active { + background: var(--indigo4); + box-shadow: none; + } +} + +.application-brand { + margin-bottom: 24px !important; + +} + +.current-seleted-route { + background: #E6EDFE !important; +} + +.dark-theme { + .current-seleted-route { + background: #1C274F !important; + } +} + +.folder-info { + display: contents; + font-weight: 500 !important; + display: flex; + align-items: center; + letter-spacing: -0.02em; + text-transform: uppercase; + color: var(-—slate9) !important; + + + p { + color: var(-—slate9) !important; + } } \ No newline at end of file diff --git a/frontend/src/_styles/onboarding.scss b/frontend/src/_styles/onboarding.scss index 192f2bf2e3..e08fbb69e5 100644 --- a/frontend/src/_styles/onboarding.scss +++ b/frontend/src/_styles/onboarding.scss @@ -29,7 +29,6 @@ font-weight: 400; font-size: 14px; line-height: 20px; - font-family: 'Roboto'; text-align: center; margin-bottom: 32px; } @@ -198,7 +197,6 @@ .info-screen-description { margin-bottom: 0px !important; - font-family: 'Roboto'; font-style: normal; font-weight: 400; font-size: 14px; diff --git a/frontend/src/_styles/popover.scss b/frontend/src/_styles/popover.scss index 83936c339f..fee9c23cb6 100644 --- a/frontend/src/_styles/popover.scss +++ b/frontend/src/_styles/popover.scss @@ -9,6 +9,10 @@ box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); border-radius: 0 8px 8px 0; overflow: auto; + + select { + cursor: pointer; + } } .PopoverContent-dark { @@ -34,7 +38,7 @@ } .PopoverContent.dark{ - background-color: #1F2936; + background-color: #1F2936 !important; .dark.leftsidebar-panel-header{ .text-muted{ color: #C0C8CC !important; diff --git a/frontend/src/_styles/queryManager.scss b/frontend/src/_styles/queryManager.scss index 9819da51be..2e8c68d8d9 100644 --- a/frontend/src/_styles/queryManager.scss +++ b/frontend/src/_styles/queryManager.scss @@ -1,5 +1,6 @@ @import "./colors.scss"; $border-radius: 4px; + .query-manager { user-select: none; @@ -35,6 +36,7 @@ $border-radius: 4px; font-weight: 600; } } + .query-pane { scrollbar-width: none; } @@ -91,7 +93,8 @@ $border-radius: 4px; .query-copy-button { display: none; } - .query-rename-delete-btn{ + + .query-rename-delete-btn { display: none; align-items: center; gap: 8px; @@ -100,6 +103,7 @@ $border-radius: 4px; width: 44px; height: 20px; } + .query-name { white-space: nowrap; overflow: hidden; @@ -108,67 +112,77 @@ $border-radius: 4px; display: flex; font-weight: 400; } - .query-name-input-field{ + + .query-name-input-field { flex: 1; width: 100%; height: 20px !important; } - .query-icon{ + + .query-icon { margin: auto 8px auto 12px; width: 21.33px; height: 21.33px; padding: 1.33px; - svg{ + + svg { width: 20px !important; height: 20px !important; } } - .delete-query,.rename-query{ - // display: none; - span{ + + .delete-query, + .rename-query { + + span { width: 16px; height: 16px; padding: 1.33px 2px; } } + &:hover { - background: $color-light-slate-03 !important; - + background: $color-light-slate-03 !important; + } - .query-row-query-name{ - padding-top: '4px'; + + .query-row-query-name { + padding-top: '4px'; padding-bottom: '4px'; transition-delay: '10ms'; } } - .query-row.border{ - border-color: $color-light-indigo-10 !important; + + .query-row.border { + border-color: $color-light-indigo-10 !important; } + .query-row-selected { - background:$color-light-indigo-04 !important; - // .delete-query,.rename-query{ - // display: block; - // } - .delete-query{ - svg:hover{ - path{ + background: $color-light-indigo-04 !important; + + .delete-query { + svg:hover { + path { fill: $color-light-tomato-09; } } } - .rename-query{ - svg:hover{ - path{ + + .rename-query { + svg:hover { + path { fill: $color-light-slate-11; } } } + &:hover { - background: $color-light-slate-03 !important; + background: $color-light-slate-03 !important; } - .rename-query.display-none{ + + .rename-query.display-none { visibility: hidden; } } @@ -215,16 +229,19 @@ $border-radius: 4px; max-height: 44px; height: 44px; font-weight: 500 !important; - .query-manager-header-query-name{ + + .query-manager-header-query-name { color: $color-light-slate-12; } - .ellipsis{ - width: 100px; + + .ellipsis { + width: 100px; white-space: nowrap; - overflow: hidden; + overflow: hidden; text-overflow: ellipsis; } - .query-header-buttons{ + + .query-header-buttons { gap: 8px; } } @@ -243,6 +260,7 @@ $border-radius: 4px; -ms-overflow-style: none; /* Internet Explorer 10+ */ user-select: none; + &::-webkit-scrollbar { /* WebKit */ width: 0; @@ -266,34 +284,40 @@ $border-radius: 4px; justify-content: center; padding: 8px 12px; gap: 12px; - .queries-search{ + + .queries-search { width: 172px !important; height: 28px !important; - .query-manager-search-box-wrapper{ - - .input-icon{ - + + .query-manager-search-box-wrapper { + + .input-icon { + margin-bottom: 0 !important; - .input-icon-addon{ + + .input-icon-addon { margin: 6px 0 6px 8px; padding: 1.33px 0 1.33px 1.33px; justify-content: normal; - width: 16px ; + width: 16px; height: 16px; min-width: 16px; align-items: center; - .icon{ + + .icon { width: 13.33px; height: 13.33px; stroke: $color-light-slate-08; } } - .form-control{ + + .form-control { padding-top: 4px !important; padding-bottom: 4px !important; - padding-right: 8px ; + padding-right: 8px; } - input{ + + input { height: 28px; font-size: 12px; font-weight: 400; @@ -301,31 +325,36 @@ $border-radius: 4px; border: 0; color: $color-light-slate-12; padding-left: 12px !important; - &::placeholder{ + + &::placeholder { color: $color-light-slate-09; } - &:focus{ + + &:focus { height: 28px; - border:1px solid $color-light-indigo-09 ; + border: 1px solid $color-light-indigo-09 ; background-color: $color-light-indigo-02; box-shadow: 0px 0px 0px 2px #C6D4F9 !important; overflow: hidden; text-overflow: clip; } - + } - .form-control:not(:first-child){ + + .form-control:not(:first-child) { padding-left: 2rem !important; - + } } - .input-icon-addon.end{ + + .input-icon-addon.end { display: flex; width: 20px; height: 20px; padding: 2px; margin: 4px 10px 4px 8px !important; - div{ + + div { background-color: #f0f4ff; width: 16px; height: 16px; @@ -333,35 +362,40 @@ $border-radius: 4px; display: flex; border-radius: 2px; } + svg { width: auto !important; height: auto !important; - stroke : $color-light-indigo-09 !important; - + stroke: $color-light-indigo-09 !important; + } } } - + } - .queries-search.theme-dark{ - .query-manager-search-box-wrapper{ - .input-icon-addon:first-child{ - .icon{ + + .queries-search.theme-dark { + .query-manager-search-box-wrapper { + .input-icon-addon:first-child { + .icon { stroke: #ffffff !important; } } - input{ - color : inherit; - &::placeholder{ + + input { + color: inherit; + + &::placeholder { color: inherit; } - &:focus{ + + &:focus { height: 28px; - border:1px solid $color-light-indigo-09 ; + border: 1px solid $color-light-indigo-09 ; background-color: inherit; box-shadow: 0px 0px 0px 2px #C6D4F9 !important; } - + } } } @@ -371,7 +405,8 @@ $border-radius: 4px; width: 0; background: transparent; } - .query-list{ + + .query-list { display: flex; flex-direction: column; justify-content: center; @@ -379,7 +414,8 @@ $border-radius: 4px; padding: 8px; gap: 2px; width: 100%; - .mute-text{ + + .mute-text { color: #6B7787; } } @@ -414,9 +450,11 @@ $border-radius: 4px; height: fit-content; width: 100%; margin-top: 20px; - .codehinter-default-input{ + + .codehinter-default-input { border: 0 !important; } + .row { height: inherit; @@ -448,24 +486,27 @@ $border-radius: 4px; .tab-content-wrapper { display: flex; flex-direction: column; - .query-number{ + + .query-number { max-height: 32px; flex: 0 0 32px; background-color: #F8F9FA; color: #000; } - .delete-field-option{ - max-height: 32px; - flex: 0 0 28px; + + .delete-field-option { + max-height: 32px; + flex: 0 0 28px; background: #ffffff; max-width: 28px !important; width: 28px; } - .code-hinter.codehinter-default-input{ + + .code-hinter.codehinter-default-input { border: 1px solid transparent !important; } - .code-hinter.codehinter-default-input:focus-within{ - // box-shadow: 0px 0px 0px 1px #C6D4F9 !important; + + .code-hinter.codehinter-default-input:focus-within { border: 1px solid #3E63DD !important; background-color: #F8FAFF; border-radius: 0; @@ -485,20 +526,23 @@ $border-radius: 4px; justify-content: space-between; width: 100%; } - .fields-container{ - .field:nth-child(3){ + + .fields-container { + .field:nth-child(3) { border-left: 1px solid $color-light-slate-07; border-right: 1px solid $color-light-slate-07; } } - .CodeMirror{ + + .CodeMirror { border-radius: 0 !important; font-size: 12px; } } + .content-title { - p{ - margin-right: 10px; + p { + margin-right: 10px; color: $color-dark-slate-12; } @@ -514,15 +558,18 @@ $border-radius: 4px; color: #ffffff !important; } } - .tab-content-wrapper{ - .query-number{ + + .tab-content-wrapper { + .query-number { background-color: inherit !important; color: #fff !important; } - .delete-field-option{ + + .delete-field-option { background: transparent !important; } - .code-hinter.codehinter-default-input:focus-within{ + + .code-hinter.codehinter-default-input:focus-within { background-color: inherit !important; } } @@ -530,26 +577,31 @@ $border-radius: 4px; .list-group-item.active { color: $white !important; } - .fields-container{ - .field{ + + .fields-container { + .field { border: 0 !important; outline: none !important; } - .field:nth-child(3){ - border-left: 1px solid $color-dark-slate-07 !important; - border-right: 1px solid $color-dark-slate-07 !important; + + .field:nth-child(3) { + border-left: 1px solid $color-dark-slate-07 !important; + border-right: 1px solid $color-dark-slate-07 !important; } } - .cm-s-monokai.CodeMirror{ + + .cm-s-monokai.CodeMirror { background-color: inherit; } - .CodeMirror{ + + .CodeMirror { border-radius: 0 !important; font-size: 12px; border: 0 !important; } + .content-title { - p{ + p { color: #a3a3a3 !important; } @@ -572,14 +624,16 @@ $border-radius: 4px; color: $color-light-slate-11; display: flex; align-items: center; + span { padding: 6px 8px; } } + .list-group-item:hover { - color: $color-light-slate-12 !important; - background-color: $color-light-slate-03 !important; + color: $color-light-slate-12 !important; + background-color: $color-light-slate-03 !important; border-radius: 6px; } @@ -591,33 +645,37 @@ $border-radius: 4px; background-color: transparent !important; color: $color-light-indigo-09; z-index: inherit !important; - border-bottom: 2px solid $color-light-indigo-09 !important; + border-bottom: 2px solid $color-light-indigo-09 !important; } - .list-group-item.active:hover{ - background-color: $color-light-indigo-03 !important; + + .list-group-item.active:hover { + background-color: $color-light-indigo-03 !important; border-radius: 6px 6px 0 0; - color: $color-light-indigo-09 !important; - } + color: $color-light-indigo-09 !important; + } } -.query-pane-restapi-tabs.dark{ - .query-pane-rest-api-keys-list-group{ + +.query-pane-restapi-tabs.dark { + .query-pane-rest-api-keys-list-group { .list-group-item { color: #9E9EA8 !important; } + .list-group-item:hover { - color: $color-dark-slate-12 !important; - background-color: $color-dark-slate-03 !important; + color: $color-dark-slate-12 !important; + background-color: $color-dark-slate-03 !important; } - + .list-group-item.active { - color: $color-dark-indigo-09 !important; + color: $color-dark-indigo-09 !important; border-bottom: 2px solid $color-dark-indigo-09 !important; } - .list-group-item.active:hover{ - background-color: $color-dark-indigo-03 !important; - color: $color-dark-indigo-09 !important; + + .list-group-item.active:hover { + background-color: $color-dark-indigo-03 !important; + color: $color-dark-indigo-09 !important; } - + } } @@ -633,12 +691,13 @@ $border-radius: 4px; cursor: pointer; font-weight: 500; font-size: 12px; - padding:0 !important; + padding: 0 !important; height: 28px; color: $color-light-slate-11; border-radius: 6px; display: flex; align-items: center; + span { padding: 6px 8px; } @@ -647,9 +706,10 @@ $border-radius: 4px; color: #000; } } + .list-group-item:hover { - color: $color-light-slate-12 !important; - background-color: $color-light-slate-03 !important; + color: $color-light-slate-12 !important; + background-color: $color-light-slate-03 !important; border-radius: 6px; } @@ -657,19 +717,20 @@ $border-radius: 4px; margin-top: 0; } - + .list-group-item.active { background-color: transparent !important; - color: $color-light-indigo-09 !important; + color: $color-light-indigo-09 !important; z-index: inherit !important; border-bottom: 2px solid $color-light-indigo-09 !important; border-radius: 0; transition-delay: 5ms; } - .list-group-item.active:hover{ - background-color: $color-light-indigo-03 !important; + + .list-group-item.active:hover { + background-color: $color-light-indigo-03 !important; border-radius: 6px 6px 0 0; - color: $color-light-indigo-09 !important; + color: $color-light-indigo-09 !important; } } @@ -677,25 +738,28 @@ $border-radius: 4px; list-group-item { color: $color-dark-slate-11 !important; } + .list-group-item:hover { - color: $color-dark-slate-12 !important; - background-color: $color-dark-slate-03 !important; + color: $color-dark-slate-12 !important; + background-color: $color-dark-slate-03 !important; } .list-group-item.active { - color: $color-dark-indigo-09 !important; + color: $color-dark-indigo-09 !important; border-bottom: 2px solid $color-dark-indigo-09 !important; } - .list-group-item.active:hover{ - background-color: $color-dark-indigo-03 !important; - color: $color-dark-indigo-09 !important; + + .list-group-item.active:hover { + background-color: $color-dark-indigo-03 !important; + color: $color-dark-indigo-09 !important; } } + /** * *Stripe Query Select-search and OpenApi */ - .stripe-operation-options .select-search__row .col-md-8 { +.stripe-operation-options .select-search__row .col-md-8 { margin-left: 45px !important; } @@ -706,35 +770,47 @@ $border-radius: 4px; border-radius: $border-radius !important; } } -.stripe-fields-row,.openApi-fields-row{ - .path-fields:first-child,.request-body-fields:first-child,.query-fields:first-child{ + +.stripe-fields-row, +.openApi-fields-row { + + .path-fields:first-child, + .request-body-fields:first-child, + .query-fields:first-child { margin-top: 12px !important; } - .field-width-179{ + + .field-width-179 { width: 179px; height: 32px; } - .field-width-28{ + + .field-width-28 { width: 28px; height: 32px; padding: 12.5px 10.5px; } - .input-group-parent-container{ - border: 1px solid $color-light-slate-07 !important; + + .input-group-parent-container { + border: 1px solid $color-light-slate-07 !important; border-radius: 6px !important; overflow: hidden; - &>.input-group-wrapper{ + + &>.input-group-wrapper { border-bottom: 1px solid $color-light-slate-07; } - &>.input-group-wrapper:last-child{ + + &>.input-group-wrapper:last-child { border-bottom: 0 !important; } } - .input-group-wrapper{ + + .input-group-wrapper { overflow: hidden; - .input-group{ - .field{ - .form-control{ + + .input-group { + .field { + .form-control { height: 32px; border: 0 !important; border-radius: 0 !important; @@ -743,139 +819,172 @@ $border-radius: 4px; font-size: 12px; line-height: 20px; } - .code-hinter-col{ + + .code-hinter-col { margin-bottom: 0 !important; - .code-hinter-wrapper{ + + .code-hinter-wrapper { border-width: 0 1px 0 1px; border-style: solid; border-color: $color-light-slate-07; } - .code-hinter.codehinter-default-input{ + + .code-hinter.codehinter-default-input { border: none; - padding: 0 ; + padding: 0; border-radius: 0 !important; overflow: auto; - .CodeMirror.cm-s-duotone-light.CodeMirror-wrap{ + + .CodeMirror.cm-s-duotone-light.CodeMirror-wrap { border: 1px solid transparent !important; margin: 0 1px; overflow: hidden !important; } - .CodeMirror.cm-s-duotone-light.CodeMirror-wrap:focus-within{ - width: 99.8% !important; - background-color:#F8FAFF !important; - border : 1px solid #3E63DD !important; + + .CodeMirror.cm-s-duotone-light.CodeMirror-wrap:focus-within { + width: 99.8% !important; + background-color: #F8FAFF !important; + border: 1px solid #3E63DD !important; margin: 0 1px; border-radius: 0; } - .CodeMirror-line{ + + .CodeMirror-line { color: #000; font-size: 12px; line-height: 20px; font-weight: 400; } } - .cm-s-duotone-light.CodeMirror{ + + .cm-s-duotone-light.CodeMirror { background-color: inherit !important; } } } } } - .text-heading{ + + .text-heading { color: $color-light-slate-12; font-weight: 400; line-height: 20px; } - .request-body-fields,.path-fields,.query-fields{ + + .request-body-fields, + .path-fields, + .query-fields { margin-top: 28px; } - .stripe-operation-options{ - p{ + + .stripe-operation-options { + p { margin-bottom: 0 !important; margin-top: 12px !important; display: inline-block !important; } } } -.openApi-fields-row{ - .path-fields:first-child,.request-body-fields:first-child,.query-fields:first-child{ + +.openApi-fields-row { + + .path-fields:first-child, + .request-body-fields:first-child, + .query-fields:first-child { margin-top: 28px !important; } } -.stripe-fields-row.theme-dark,.openApi-fields-row.theme-dark{ - .form-control{ + +.stripe-fields-row.theme-dark, +.openApi-fields-row.theme-dark { + .form-control { background-color: inherit !important; color: inherit !important; } - .field-width-28{ - svg{ - path{ + + .field-width-28 { + svg { + path { fill: #fff; } } } - .text-heading{ + + .text-heading { color: inherit; } - .code-hinter-wrapper{ + + .code-hinter-wrapper { border-color: #ffffff17 !important; } - .input-group-parent-container{ - border: 1px solid #ffffff17 !important; - &>.input-group-wrapper{ + + .input-group-parent-container { + border: 1px solid #ffffff17 !important; + + &>.input-group-wrapper { border-bottom: 1px solid #ffffff17; } - &>.input-group-wrapper:last-child{ + + &>.input-group-wrapper:last-child { border-bottom: 0 !important; } } - .CodeMirror-line{ + + .CodeMirror-line { color: inherit !important; } - .CodeMirror.cm-s-monokai.CodeMirror-wrap{ + + .CodeMirror.cm-s-monokai.CodeMirror-wrap { border: 1px solid transparent; margin: 0 1px; } - .CodeMirror.cm-s-monokai.CodeMirror-wrap:focus-within{ + + .CodeMirror.cm-s-monokai.CodeMirror-wrap:focus-within { width: 99.8% !important; - border : 1px solid #3E63DD !important; - margin: 0 1px ; + border: 1px solid #3E63DD !important; + margin: 0 1px; border-radius: 0; } } -.data-pane{ - .queries-container.theme-dark{ - .query-row{ +.data-pane { + .queries-container.theme-dark { + .query-row { color: #f4f6fa; } - .query-row-selected{ - background: #2b3546 !important; + + .query-row-selected { + background: #2b3546 !important; } + .query-row:hover { background: #404d66 !important; } - .delete-query{ - svg{ - path{ - fill: $color-dark-tomato-10 !important; + + .delete-query { + svg { + path { + fill: $color-dark-tomato-10 !important; } } - svg:hover{ - path{ - fill: $color-dark-tomato-09 !important; + + svg:hover { + path { + fill: $color-dark-tomato-09 !important; } } } - .rename-query{ - svg{ - path{ - fill: $color-dark-slate-12 !important; + + .rename-query { + svg { + path { + fill: $color-dark-slate-12 !important; } } - svg:hover{ - path{ - fill: $color-dark-slate-11 !important; + + svg:hover { + path { + fill: $color-dark-slate-11 !important; } } } @@ -885,111 +994,129 @@ $border-radius: 4px; } //query manager -.query-manager{ - .query-name-breadcrum{ +.query-manager { + .query-name-breadcrum { cursor: pointer; - .breadcrum-rename-query-icon{ + + .breadcrum-rename-query-icon { display: none; } - &:hover{ - .breadcrum-rename-query-icon{ + + &:hover { + .breadcrum-rename-query-icon { display: inline-flex; height: 14px; width: 14px; margin-left: 8px; padding: 1.75px; - &:hover{ - svg{ - path{ - fill: $color-light-slate-11 !important; + + &:hover { + svg { + path { + fill: $color-light-slate-11 !important; } } } } } } - .code-hinter.codehinter-default-input{ - &:hover{ - background-color: #FBFCFD ; + + .code-hinter.codehinter-default-input { + &:hover { + background-color: #FBFCFD; border-radius: 6px; } } - .breadcrum{ - width: 15.33px ; - height: 15.33px ; + + .breadcrum { + width: 15.33px; + height: 15.33px; padding: 3px 5px; display: flex; } + input[type=checkbox]:checked { background-color: $color-light-indigo-09; - + } + input[type=checkbox]:checked:active { background-color: $color-light-indigo-09; box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } + input[type=checkbox] { background-color: $color-light-slate-07; cursor: pointer !important; border: 0; } + input[type=checkbox]:active { background-color: $color-light-indigo-04; box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } + input[type=checkbox]:hover { background-color: $color-light-indigo-05; - + } + input[type=checkbox]:checked:hover { background-color: $color-light-indigo-11; - + } - .CodeMirror-placeholder{ + + .CodeMirror-placeholder { margin-top: 0 !important; } - .preview-section{ - ul{ + + .preview-section { + ul { margin: 0 !important; padding: 0.5em !important; } - .preview-default-container{ + + .preview-default-container { user-select: text; background-color: #F8F9FA; border: 0 0 6px 6px; height: 52px, } - .tab-pane.active{ - div{ + + .tab-pane.active { + div { background-color: #F8F9FA !important; border-radius: 0 0 6px 6px !important; } - ul{ - background-color: transparent !important ; + + ul { + background-color: transparent !important; } } } - .query-manager-border-color{ - border-color: $color-light-slate-05 !important; + + .query-manager-border-color { + border-color: $color-light-slate-05 !important; } - .query-details{ - .form-label{ - color: $color-light-slate-12 !important; + .query-details { + .form-label { + color: $color-light-slate-12 !important; font-size: 12px; font-weight: 400; line-height: 20px; } } - - .rest-methods-url{ - .code-hinter.codehinter-default-input{ + + .rest-methods-url { + .code-hinter.codehinter-default-input { border-style: solid !important; - border-color: $color-light-slate-07 !important; + border-color: $color-light-slate-07 !important; border-radius: 0 6px 6px 0 !important; - &:focus-within{ + + &:focus-within { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: #F8FAFF; @@ -997,346 +1124,431 @@ $border-radius: 4px; z-index: 1 !important; } } - .CodeMirror.CodeMirror-wrap{ + + .CodeMirror.CodeMirror-wrap { background-color: transparent !important; - .cm-variable,.cm-comment{ + + .cm-variable, + .cm-comment { font-size: 12px !important; - color: $color-light-slate-12 !important; + color: $color-light-slate-12 !important; } } } - .rest-api-methods-select-element-container{ + + .rest-api-methods-select-element-container { display: flex; flex-direction: row; - .col.code-hinter-col{ + + .col.code-hinter-col { margin-bottom: 0 !important; - + } } - .advanced-options-container{ - .advance-options-input-form-container{ - margin-top: 24px ; - margin-bottom: 24px ; + .advanced-options-container { + .advance-options-input-form-container { + margin-top: 24px; + margin-bottom: 24px; } - .form-check{ + + .form-check { margin-bottom: 0 !important; } - .CodeMirror-code{ + + .CodeMirror-code { color: $color-light-grass-11 ; } - .form-label{ - color: $color-light-slate-11 !important; - } - .form-check-label{ + + .form-label { + color: $color-light-slate-11 !important; + } + + .form-check-label { color: $color-light-slate-12; } - .cm-s-monokai.CodeMirror,.query-manager-input-elem>input{ - color: $color-light-slate-12 !important; + + .cm-s-monokai.CodeMirror, + .query-manager-input-elem>input { + color: $color-light-slate-12 !important; } - .codehinter-default-input:focus-within,.query-manager-input-elem>input:focus{ + + .codehinter-default-input:focus-within, + .query-manager-input-elem>input:focus { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: #F8FAFF; } - .code-hinter.codehinter-default-input,.query-manager-input-elem > input{ + + .code-hinter.codehinter-default-input, + .query-manager-input-elem>input { height: 32px !important; font-weight: 400; } - .query-manager-events{ - .card{ - .event-handler-display,.event-name-display{ - color:$color-light-slate-12 !important; + + .query-manager-events { + .card { + + .event-handler-display, + .event-name-display { + color: $color-light-slate-12 !important; } - .text-danger{ - svg{ - path{ - fill:$color-light-tomato-09 !important; + + .text-danger { + svg { + path { + fill: $color-light-tomato-09 !important; } } - &:hover{ - svg{ - path{ - fill:$color-light-tomato-10 !important; + + &:hover { + svg { + path { + fill: $color-light-tomato-10 !important; } } } } - + } - .inspector-add-button{ + + .inspector-add-button { background-color: transparent; font-weight: 400 !important; font-size: 12px !important; - color: $color-light-indigo-09 !important; - &:hover{ + color: $color-light-indigo-09 !important; + + &:hover { background-color: #E6EDFE !important; color: #3451B2 !important; } } } } - .transformation-editor{ + + .transformation-editor { margin-top: 28px; - & > div.rounded-3{ + + &>div.rounded-3 { padding: 0 24px !important; } - .tranformation-label{ + + .tranformation-label { color: $color-light-slate-12; } - .CodeMirror-scroll,.CodeMirror-gutters{ + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: $color-light-slate-02 ; } - .transformation-language-select-wrapper{ + + .transformation-language-select-wrapper { background: $color-light-slate-04; - border: 1px solid $color-light-slate-07 !important; + border: 1px solid $color-light-slate-07 !important; border-radius: 0; border-radius: 6px 0 0 6px; - span{ + + span { color: $color-light-slate-11; } } } - .runjs-editor{ - .CodeMirror-scroll,.CodeMirror-gutters{ + + .runjs-editor { + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: $color-light-slate-02; } - .query-hinter{ + + .query-hinter { textarea:first-of-type { opacity: 0.1; // Added to avoid displaying textarea while switching from one RunJS Query to another } } } - .query-editor-dynamic-form-container{ + + .query-editor-dynamic-form-container { font-weight: 400; - .css-1goth4y-control:hover{ + + .css-1goth4y-control:hover { background-color: #FBFCFD; border-radius: 6px; } - .css-1goth4y-control:active{ - box-shadow: 0px 0px 0px 2px #C6D4F9 !important; - border: 1px solid #3E63DD !important; - border-radius: 6px; - background-color: #F8FAFF; - } - .css-1goth4y-control{ - cursor: pointer !important; - } - - .query-hinter{ - .CodeMirror-scroll,.CodeMirror-gutters{ - background-color: $color-light-slate-02 ; - - } - } - .codehinter-plugins{ - .code-hinter.codehinter-plugins{ - height: 32px !important; - padding: 2px 0 !important; - font-size: 12px !important; - .CodeMirror-lines{ - padding: 0 !important; - } - } - } - .CodeMirror-line{ - font-size: 12px; - } - .code-hinter.codehinter-plugins:focus-within{ + + .css-1goth4y-control:active { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; border-radius: 6px; background-color: #F8FAFF; } - & > .row:first-child{ - .my-2{ + + .css-1goth4y-control { + cursor: pointer !important; + } + + .query-hinter { + + .CodeMirror-scroll, + .CodeMirror-gutters { + background-color: $color-light-slate-02 ; + + } + } + + .codehinter-plugins { + .code-hinter.codehinter-plugins { + height: 32px !important; + padding: 2px 0 !important; + font-size: 12px !important; + + .CodeMirror-lines { + padding: 0 !important; + } + } + } + + .CodeMirror-line { + font-size: 12px; + } + + .code-hinter.codehinter-plugins:focus-within { + box-shadow: 0px 0px 0px 2px #C6D4F9 !important; + border: 1px solid #3E63DD !important; + border-radius: 6px; + background-color: #F8FAFF; + } + + &>.row:first-child { + .my-2 { margin: 0 !important; } } - & > .row:not(:first-child){ - .my-2{ - margin: 20px 0 0 0 !important; + + &>.row:not(:first-child) { + .my-2 { + margin: 20px 0 0 0 !important; } } - .code-hinter-wrapper{ + + .code-hinter-wrapper { border-radius: 6px; } } - .delete-field-option:hover{ - svg{ - path{ - fill: $color-light-tomato-10 !important; + + .delete-field-option:hover { + svg { + path { + fill: $color-light-tomato-10 !important; } } } - .add-tabs:hover{ - svg{ - path{ - fill: $color-light-indigo-10 !important; + + .add-tabs:hover { + svg { + path { + fill: $color-light-indigo-10 !important; } } } - .query-datasource-card-container{ - .query-datasource-card{ - color: $color-light-slate-12 !important; + + .query-datasource-card-container { + .query-datasource-card { + color: $color-light-slate-12 !important; font-weight: 400; font-size: 12px; height: 32px; - svg{ + + svg { width: 16px !important; height: 16px !important; } } } - + } -.query-manager.theme-dark{ - .breadcrum-rename-query-icon{ - svg{ - path{ +.query-manager.theme-dark { + .breadcrum-rename-query-icon { + svg { + path { fill: $color-dark-slate-12; } } - &:hover{ - svg{ - path{ + + &:hover { + svg { + path { fill: $color-dark-slate-11; } } } } - .preview-section{ - .preview-default-container{ + + .preview-section { + .preview-default-container { background-color: #272822; } - .tab-pane{ - div{ + + .tab-pane { + div { background-color: #272822 !important; } } } - .query-manager-border-color{ + + .query-manager-border-color { border-color: #ffffff17 !important; } - .code-hinter.codehinter-default-input,.query-select-dropdown~.css-1tkif1k-container:focus{ - &:hover{ + + .code-hinter.codehinter-default-input, + .query-select-dropdown~.css-1tkif1k-container:focus { + &:hover { background-color: #282926 !important; } } - .query-details{ - .form-label{ + + .query-details { + .form-label { color: #f4f6fa !important; font-size: 12px; font-weight: 400; line-height: 20px; } } - .row.header{ - .breadcrum{ - svg{ - path{ - fill: #4C5155 ; + + .row.header { + .breadcrum { + svg { + path { + fill: #4C5155; } } } - .query-manager-header-query-name{ + + .query-manager-header-query-name { color: #f4f6fa !important; } } - .transformation-editor{ - .css-5g0ati-control{ - border: 0 ; + + .transformation-editor { + .css-5g0ati-control { + border: 0; border-radius: 0 4px 4px 0; background: #2B3546 !important; - &:focus-within{ - border: 1px solid $color-light-indigo-09 !important; + + &:focus-within { + border: 1px solid $color-light-indigo-09 !important; } } - .tranformation-label{ + + .tranformation-label { color: #fff; } - .css-ppbcid-singleValue{ - color: $color-dark-slate-12 !important; + + .css-ppbcid-singleValue { + color: $color-dark-slate-12 !important; } - .CodeMirror-scroll,.CodeMirror-gutters{ + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: inherit; } - .transformation-language-select-wrapper{ + + .transformation-language-select-wrapper { background: $color-dark-slate-04; - border-style: solid; + border-style: solid; border-color: #cccccc; border-width: 1px 0 1px 1px !important; - span{ + + span { color: $color-dark-slate-11; } } } - .advanced-options-container{ - .CodeMirror-code{ - color: $color-dark-grass-11 !important; + + .advanced-options-container { + .CodeMirror-code { + color: $color-dark-grass-11 !important; } - .form-label{ + + .form-label { color: #f4f6fa !important; - } - .form-check-label{ + } + + .form-check-label { color: $color-dark-slate-12; } - .cm-s-monokai.CodeMirror,.query-manager-input-elem>input{ - color: $color-dark-slate-12 !important; + + .cm-s-monokai.CodeMirror, + .query-manager-input-elem>input { + color: $color-dark-slate-12 !important; } - .codehinter-default-input:focus-within,.query-manager-input-elem>input:focus{ + + .codehinter-default-input:focus-within, + .query-manager-input-elem>input:focus { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: inherit; } - .query-manager-events{ - .card{ - .event-handler-display,.event-name-display{ - color:inherit !important; + + .query-manager-events { + .card { + + .event-handler-display, + .event-name-display { + color: inherit !important; } } - .text-danger{ - svg{ - path{ - fill:$color-dark-tomato-09 !important; + + .text-danger { + svg { + path { + fill: $color-dark-tomato-09 !important; } } - &:hover{ - svg{ - path{ - fill:$color-dark-tomato-10 !important; + + &:hover { + svg { + path { + fill: $color-dark-tomato-10 !important; } } } } - .inspector-add-button{ + + .inspector-add-button { background-color: inherit; color: $color-dark-indigo-09; - &:hover{ + + &:hover { background-color: inherit; color: inherit; } } } } - .rest-api-methods-select-element-container{ - .col.code-hinter-col{ + + .rest-api-methods-select-element-container { + .col.code-hinter-col { margin-bottom: 0 !important; } - .css-17xqmvg-container{ - .css-5g0ati-control{ + + .css-17xqmvg-container { + .css-5g0ati-control { cursor: pointer; border-radius: 6px 0 0 6px !important; - svg{ - path{ - fill: $color-dark-slate-11 !important; + + svg { + path { + fill: $color-dark-slate-11 !important; } } - &:focus-within{ + + &:focus-within { border: 1px solid $color-dark-indigo-09; background-color: inherit; box-shadow: 0px 0px 0px 2px #C6D4F9; @@ -1344,19 +1556,23 @@ $border-radius: 4px; } } } - .rest-methods-url{ - .code-hinter.codehinter-default-input{ + + .rest-methods-url { + .code-hinter.codehinter-default-input { border: solid inherit !important; border-radius: 0 6px 6px 0 !important; - .CodeMirror.cm-s-monokai.CodeMirror-wrap{ + + .CodeMirror.cm-s-monokai.CodeMirror-wrap { background-color: transparent !important; - .cm-variable,.cm-comment{ + + .cm-variable, + .cm-comment { font-size: 12px !important; color: #f8f8f2 !important; } } - - &:focus-within{ + + &:focus-within { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; background-color: inherit !important; @@ -1364,60 +1580,76 @@ $border-radius: 4px; } } - .runjs-editor{ - .CodeMirror-scroll,.CodeMirror-gutters{ + + .runjs-editor { + + .CodeMirror-scroll, + .CodeMirror-gutters { background-color: inherit; } } - .query-editor-dynamic-form-container{ - .query-hinter{ - .CodeMirror-scroll,.CodeMirror-gutters{ - background-color: transparent !important ; + + .query-editor-dynamic-form-container { + .query-hinter { + + .CodeMirror-scroll, + .CodeMirror-gutters { + background-color: transparent !important; } } - .css-5g0ati-control{ + + .css-5g0ati-control { cursor: pointer !important; } - .css-5g0ati-control:active{ + + .css-5g0ati-control:active { box-shadow: 0px 0px 0px 2px #C6D4F9 !important; border: 1px solid #3E63DD !important; border-radius: 6px; } - .code-hinter.codehinter-plugins:focus-within{ - background-color: inherit ; + + .code-hinter.codehinter-plugins:focus-within { + background-color: inherit; } } - .delete-field-option:hover{ - svg{ - path{ - fill: $color-dark-tomato-10 !important; + + .delete-field-option:hover { + svg { + path { + fill: $color-dark-tomato-10 !important; } } } - .add-tabs:hover{ - svg{ - path{ - fill: $color-dark-indigo-10 !important; + + .add-tabs:hover { + svg { + path { + fill: $color-dark-indigo-10 !important; } } } - .query-datasource-card-container{ - .query-datasource-card{ + + .query-datasource-card-container { + .query-datasource-card { color: #fff !important; } } } -.query-icon-wrapper{ + +.query-icon-wrapper { width: 16px; height: 16px; } -.query-manager-btn-svg-wrapper{ + +.query-manager-btn-svg-wrapper { padding: 2.67px; } -.rest-api-delete-field-option{ + +.rest-api-delete-field-option { padding: 1.33px 2px; } -.rest-api-add-field-svg{ + +.rest-api-add-field-svg { width: 18px; height: 18px; padding: 3px; @@ -1425,21 +1657,22 @@ $border-radius: 4px; } // custom-toggle-switch for query manager -.custom-toggle-switch{ +.custom-toggle-switch { display: flex; + .switch { position: relative; display: inline-block; width: 28px; height: 16px; } - - .switch input { + + .switch input { opacity: 0; width: 0; height: 0; } - + .slider { position: absolute; cursor: pointer; @@ -1451,11 +1684,11 @@ $border-radius: 4px; -webkit-transition: .4s; transition: .4s; } - + .slider:before { position: absolute; content: ""; - height:12px; + height: 12px; width: 12px; left: 2px; bottom: 2px; @@ -1463,71 +1696,80 @@ $border-radius: 4px; -webkit-transition: .2s; transition: .2s; } - - input{ + + input { border: 0 !important; } - input[type=checkbox]:checked + .slider { - background-color: #3E63DD ; + + input[type=checkbox]:checked+.slider { + background-color: #3E63DD; } - input[type=checkbox]:checked:hover + .slider{ - background-color: #3451B2 ; + + input[type=checkbox]:checked:hover+.slider { + background-color: #3451B2; } - input[type=checkbox]:checked:active + .slider { - background-color: #3E63DD ; - box-shadow: 0px 0px 0px 4px #C6D4F9 ; + + input[type=checkbox]:checked:active+.slider { + background-color: #3E63DD; + box-shadow: 0px 0px 0px 4px #C6D4F9; } - - input[type=checkbox]:checked + .slider:before { + + input[type=checkbox]:checked+.slider:before { -webkit-transform: translateX(100%); -ms-transform: translateX(100%); transform: translateX(100%); } - input[type=checkbox] + .slider{ - background-color: #D7DBDF; - } - input[type=checkbox]:hover + .slider { + input[type=checkbox]+.slider { + background-color: var(--slate7); + } + + input[type=checkbox]:hover+.slider { background-color: #D9E2FC; } - - input[type=checkbox]:active + .slider { - background: #E6EDFE ; - box-shadow: 0px 0px 0px 4px #C6D4F9 ; + + input[type=checkbox]:active+.slider { + background: #E6EDFE; + box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } - + /* Rounded sliders */ .slider.round { border-radius: 12px; } - + .slider.round:before { border-radius: 50%; } } -.custom-toggle-switch.theme-dark{ - input[type=checkbox] + .slider{ + +.custom-toggle-switch.theme-dark { + input[type=checkbox]+.slider { background-color: #47505D !important; } - input[type=checkbox]:hover + .slider { + + input[type=checkbox]:hover+.slider { background-color: #D9E2FC; } - - input[type=checkbox]:active + .slider { - background: #E6EDFE ; - box-shadow: 0px 0px 0px 4px #C6D4F9 ; + + input[type=checkbox]:active+.slider { + background: #E6EDFE; + box-shadow: 0px 0px 0px 4px #C6D4F9; border-radius: 12px; } - input[type=checkbox]:checked + .slider { + + input[type=checkbox]:checked+.slider { background-color: #3E63DD !important; } - input[type=checkbox]:checked:hover + .slider{ + + input[type=checkbox]:checked:hover+.slider { background-color: #3451B2 !important; } - input[type=checkbox]:checked:active + .slider { - background-color: #3E63DD !important; - box-shadow: 0px 0px 0px 4px #C6D4F9 !important; + + input[type=checkbox]:checked:active+.slider { + background-color: #3E63DD !important; + box-shadow: 0px 0px 0px 4px #C6D4F9 !important; } } diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 15ecda0afd..5c4f23c845 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -5,18 +5,20 @@ @import "./queryManager.scss"; @import "./onboarding.scss"; @import "./components.scss"; -@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,400;1,500;1,600;1,700&display=swap'); - @import "./global-datasources.scss"; - @import "./typography.scss"; @import "./designtheme.scss"; +@import "./dropdown-custom.scss"; +@import "./ui-operations.scss"; +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,400;1,500;1,600;1,700&display=swap'); +@import 'react-loading-skeleton/dist/skeleton.css'; // variables $border-radius: 4px; + body { - font-family: "Roboto", sans-serif; + font-family: 'IBM Plex Sans'; } input, @@ -142,13 +144,13 @@ button { .editor { .header-container { max-width: 100%; - padding: 0 10px; + padding: 0 10px 0 0; min-height: 45px; } .resizer-select, .resizer-active { - border: solid 1px $primary !important; + border: solid 1px $primary !important; .top-right, .top-left, @@ -211,7 +213,7 @@ button { display: flex; min-width: 0; word-wrap: break-word; - background-color: #fff; + background-color: #ffffff; background-clip: border-box; border: 1px solid rgba(101, 109, 119, 0.16); border-radius: 4px; @@ -283,11 +285,12 @@ button { left: 0; overflow-x: hidden; flex: 1 1 auto; - background-color: #fff; + background-color: #fff !important; background-clip: border-box; border: solid rgba(0, 0, 0, 0.125); border-width: 0px 1px 3px 0px; margin-top: 0px; + padding-top: 0px; .accordion-item { border: solid rgba(101, 109, 119, 0.16); @@ -361,8 +364,8 @@ button { width: 300px; flex: 1 1 auto; top: 45px; - border-top: 1px solid #E6E8EB; - background-color: #fff; + border-top: 1px solid var(--slate7); + background-color: var(--base); background-clip: border-box; border: solid rgba(0, 0, 0, 0.125); border-width: 0px 0px 0px 1px; @@ -564,7 +567,6 @@ button { .canvas-container { scrollbar-width: auto; width: 100%; - // margin-left: 10%; } .canvas-container::-webkit-scrollbar { @@ -628,24 +630,45 @@ button { .list-group.list-group-transparent.dark .all-apps-link, .list-group-item-action.dark.active { - background-color: $dark-background !important; + background-color: $dark-background !important; } } .home-search-holder { - height: 36px; - width: 272px; + height: 20px; + width: 100%; + margin-top: 32px; + + .search-box-wrapper { + .input-icon { + .input-icon-addon { + padding-right: 6px; + } + } + } .homepage-search { + background: transparent; + color: var(--slate12); + height: 20px; + &:focus { background: none; } } } +.homepage-app-card-list-item-wrap { + row-gap: 16px; + column-gap: 32px; + display: flex; + margin-top: 22px; +} + .homepage-app-card-list-item { - max-width: 290px; + max-width: 272px; flex-basis: 33%; + padding: 0 !important; } .homepage-dropdown-style { @@ -655,11 +678,12 @@ button { margin: 0; line-height: 1.4285714; width: 100%; - padding: 0.5rem 0.75rem; + padding: 0.5rem 0.75rem !important; font-weight: 400; white-space: nowrap; border: 0; cursor: pointer; + font-size: 12px; } .homepage-dropdown-style:hover { @@ -738,9 +762,13 @@ button { } button.create-new-app-button { - background-color: $primary-light; + background-color: var(--indigo9); + } + + + .app-list { .app-card { height: 180px; @@ -751,9 +779,9 @@ button { overflow: hidden; .app-creation-time { - font-size: 12px; - line-height: 12px; - color: #61656f; + span { + color: var(--slate11) !important; + } } .app-creator { @@ -768,7 +796,6 @@ button { .app-icon-main { background-color: $primary; - border-radius: 4px; .app-icon { img { @@ -780,6 +807,12 @@ button { } } + .app-template-card-wrapper { + .card-body { + padding-left: 0px !important; + } + } + .app-title { line-height: 20px; font-size: 1rem; @@ -803,10 +836,6 @@ button { .menu-ico { cursor: pointer; - // &__open { - // background-color: #d2ddec; - // } - img { padding: 0px; height: 14px; @@ -814,10 +843,6 @@ button { vertical-align: unset; } } - - // .menu-ico:hover { - // background-color: #d2ddec; - // } } .app-card.highlight { @@ -825,20 +850,65 @@ button { box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); border: 0.5px solid $primary; - button.edit-button { - background: #ffffff; - border: 1px solid $primary-light; + .edit-button { box-sizing: border-box; - border-radius: 4px; + border-radius: 6px; color: $primary-light; + width: 136px; + height: 28px; + background: var(--indigo11) !important; + border: none; + color: var(--indigo4); + + &:hover { + background: var(--indigo10); + + } + + &:focus { + box-shadow: 0px 0px 0px 4px var(--indigo6); + background: var(--indigo10); + outline: 0; + } + + + &:active { + background: var(--indigo11); + box-shadow: none; + } } - button.launch-button { - background: $primary-light; - border: 1px solid $primary-light; + .launch-button { + box-sizing: border-box; - border-radius: 4px; - color: #ffffff; + border-radius: 6px; + color: var(--base); + width: 92px; + height: 28px; + background: var(--base); + border: 1px solid var(--slate7); + color: var(--slate12); + + &:hover { + background: var(--slate8); + color: var(--slate11); + border: 1px solid var(--slate8); + background: var(--base); + } + + &:active { + background: var(--base); + box-shadow: none; + border: 1px solid var(--slate12); + color: var(--slate12); + } + + &:focus { + background: var(--base); + color: var(--slate11); + border: 1px solid var(--slate8); + box-shadow: 0px 0px 0px 4px var(--slate6); + } } .app-title { @@ -854,6 +924,12 @@ button { .template-library-modal { font-weight: 500; + .modal-header { + background-color: var(--base) !important; + border-bottom: 1px solid var(--slate5); + + } + .modal-dialog { max-width: 90%; height: 80%; @@ -862,9 +938,12 @@ button { height: 100%; padding: 0; + .modal-body { height: 100%; padding: 0 10px; + background-color: var(--base) !important; + .container-fluid { height: 100%; @@ -886,7 +965,6 @@ button { .template-categories { .list-group-item { border: 0; - // padding-bottom: 3px; } .list-group-item.active { @@ -899,7 +977,6 @@ button { .template-app-list { .list-group-item { border: 0; - // padding-bottom: 3px; } .list-group-item.active { @@ -993,7 +1070,7 @@ button { .template-list-column, .categories-column, .modal-header { - border-color: #232e3c !important; + border-color: var(--slate5) !important; } .modal-body, @@ -1008,7 +1085,6 @@ button { .list-group-item { color: white; border: 0; - // padding-bottom: 3px; } .list-group-item:hover { @@ -1026,14 +1102,11 @@ button { .list-group-item { border: 0; color: white; - // padding-bottom: 3px; } .list-group-item:hover { border: 0; - // color: red; background-color: #232e3c; - // padding-bottom: 3px; } .list-group-item.active { @@ -1042,7 +1115,7 @@ button { } .no-results-item { - background-color: #2b394a; + background-color: var(--slate4); color: white; } } @@ -1132,32 +1205,6 @@ button { .homepage-body { .app-list { - .app-card { - .app-creation-time { - color: #afb0b2; - } - - .app-creator { - color: #c3c4cb; - } - } - - .app-card.highlight { - background-color: #2c405c; - - button.edit-button { - background: transparent; - border: 1px solid #ffffff; - color: #ffffff; - } - - button.launch-button { - background: $primary-light; - border: 1px solid $primary-light; - color: #ffffff; - } - } - .app-title { line-height: 20px; font-size: 16px; @@ -1221,7 +1268,7 @@ button { .select-search-dark input { width: 224px !important; height: 32px !important; - border-radius: $border-radius !important; + border-radius: $border-radius !important; } } @@ -1232,7 +1279,7 @@ button { .select-search__value input, .select-search-dark input { height: 32px !important; - border-radius: $border-radius !important; + border-radius: $border-radius !important; } } @@ -1287,14 +1334,13 @@ button { font-weight: 400; line-height: 1.4285714; color: #232e3c; - background-color: #fff; + background-color: #ffffff; background-clip: padding-box; border: 1px solid #dadcde; -webkit-appearance: none; -moz-appearance: none; appearance: none; - // border-radius: 0; - border-radius: $border-radius !important; + border-radius: $border-radius !important; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @@ -1313,7 +1359,7 @@ button { * Options wrapper */ .select-search__select { - background: #fff; + background: #ffffff; box-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.15); } @@ -1340,7 +1386,7 @@ button { height: 36px; width: 100%; padding: 0 16px; - background: #fff; + background: var(--base); border: none; outline: none; font-family: "Roboto", sans-serif; @@ -1361,7 +1407,7 @@ button { .select-search__option.is-highlighted.is-selected, .select-search__option.is-selected:hover { background: #2eb378; - color: #fff; + color: #ffffff; } /** @@ -1486,11 +1532,11 @@ button { .select-search-dark__input { display: block; width: 100%; - padding: 0.4375rem 0.75rem; + // padding: 0.4375rem 0.75rem; font-size: 0.875rem; font-weight: 400; line-height: 1.4285714; - color: #fff; + color: #ffffff; background-color: #2b3547; background-clip: padding-box; border: 1px solid #232e3c; @@ -1536,9 +1582,8 @@ button { height: 36px; width: 100%; padding: 0 16px; - background-color: $dark-background !important; - color: #fff !important; - border: none; + background-color: var(--base) !important; + color: #ffffff !important; outline: none; font-family: "Roboto", sans-serif; font-size: 14px; @@ -1609,7 +1654,7 @@ button { } .select-search-dark:not(.select-search-dark--multiple) .select-search-dark__input:hover { - border-color: #fff; + border-color: #ffffff; } .select-search-dark:not(.select-search-dark--multiple) .select-search-dark__select { @@ -1853,7 +1898,7 @@ button { } .form-check-input:checked { - background-color: $primary; + background-color: var(--indigo9) !important; border-color: rgba(101, 109, 119, 0.24); } @@ -1880,10 +1925,6 @@ tr:focus { } } -// .jet-container { -// // width: 100%; -// } - .select-search__option { color: rgb(90, 89, 89); } @@ -2036,7 +2077,8 @@ tr:focus { padding: 5px; } -.table-filters { +.table-filters, +.table-add-new-row { position: absolute; top: 2.85rem; width: 80%; @@ -2096,17 +2138,25 @@ tr:focus { #popover-app-menu { border-radius: 4px; width: 150px; - box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.25); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + background: var(--base); + color: var(--slate12); + border: 1px solid var(--slate3); + + .popover-arrow { + display: none; + } .popover-body { padding: 16px 12px 0px 12px; + color: var(--slate12); .field { font-weight: 500; font-size: 0.7rem; &__danger { - color: #ff6666; + color: var(--tomato9); } } } @@ -2144,7 +2194,7 @@ body { } .RichEditor-root { - background: #fff; + background: #ffffff; border: 1px solid #ddd; font-family: "Georgia", serif; font-size: 14px; @@ -2318,20 +2368,20 @@ body { .CalendarDay__selected_span:hover { background: $primary; border: 1px double $primary; - color: #fff; + color: #ffffff; } .CalendarDay__hovered_span:active, .CalendarDay__hovered_span:hover { background: $primary; border: 1px double $primary; - color: #fff; + color: #ffffff; } .CalendarDay__hovered_span { background: #83b8e7; border: 1px double #83b8e7; - color: #fff; + color: #ffffff; } .table-responsive { @@ -2456,7 +2506,7 @@ hr { font-size: 0.875rem; font-weight: 400; color: #232e3c; - background-color: #fff; + background-color: #ffffff; background-clip: padding-box; border: 1px solid #dadcde; -webkit-appearance: none; @@ -2485,7 +2535,7 @@ hr { font-size: 0.875rem; font-weight: 400; color: #232e3c; - background-color: #fff; + background-color: #ffffff; background-clip: padding-box; border: 1px solid #dadcde; border-radius: $border-radius; @@ -2516,7 +2566,7 @@ hr { .handle-content { cursor: move; - color: $white; + color: #ffffff; background: $primary; } @@ -2746,7 +2796,7 @@ input:focus-visible { .select-search__option.is-selected { background: $primary; - color: $white; + color: #ffffff; } } @@ -2757,7 +2807,7 @@ input:focus-visible { .widget-documentation-link { position: fixed; bottom: 0; - background: $white; + background: #ffffff; width: 100%; z-index: 1; } @@ -2859,31 +2909,21 @@ input:focus-visible { .theme-dark .navbar .navbar-nav .nav-link.active, .theme-dark .navbar .navbar-nav .nav-link.show, .theme-dark .navbar .navbar-nav .show>.nav-link { - color: #fff; + color: #ffffff; } - .form-check>.form-check-input:not(:checked) { - background-color: #fff; - } - - .form-switch>.form-check-input:not(:checked) { - background-color: #47505d !important; - } .form-check-label { color: white; } - .left-sidebar .active { - background: #333c48; - } .left-sidebar .left-sidebar-item { border-bottom: 1px solid #333c48; } .nav-tabs .nav-link.active { - color: #fff !important; + color: #ffffff !important; } .nav-tabs .nav-link { @@ -2891,7 +2931,7 @@ input:focus-visible { } .card-body> :last-child { - color: #fff !important; + color: #ffffff !important; } .form-control { @@ -2912,12 +2952,12 @@ input:focus-visible { .DateInput_input { background-color: #1f2936; - color: #fff; + color: #ffffff; } &.daterange-picker-widget { .DateRangePickerInput_arrow_svg { - fill: #fff; + fill: #ffffff; } } @@ -2985,7 +3025,7 @@ input:focus-visible { background-color: #1f2936; .text-muted { - color: #fff !important; + color: var(--slate09) !important; } } @@ -3012,7 +3052,7 @@ input:focus-visible { .query-list { .text-muted { - color: #fff !important; + color: #ffffff !important; } .mute-text { @@ -3020,7 +3060,6 @@ input:focus-visible { } } - .left-sidebar, .editor-sidebar { background-color: #1f2936 !important; } @@ -3035,7 +3074,7 @@ input:focus-visible { } .editor .editor-sidebar .nav-tabs .nav-link { - color: #fff; + color: #ffffff; img { filter: brightness(0) invert(1); @@ -3063,11 +3102,8 @@ input:focus-visible { } .left-sidebar { - border: solid rgba(255, 255, 255, 0.09); - border-width: 0px 1px 3px 0px; - .text-muted { - color: #fff !important; + color: #ffffff !important; } .left-sidebar-page-selector { @@ -3088,12 +3124,8 @@ input:focus-visible { } } - .folder-list { - color: #fff !important; - } - .app-title { - color: #fff !important; + color: var(--base) !important; } .RichEditor-root { @@ -3102,7 +3134,7 @@ input:focus-visible { } .app-description { - color: #fff !important; + color: #ffffff !important; } .btn-light, @@ -3138,7 +3170,7 @@ input:focus-visible { .app-users-list { .text-muted { - color: #fff !important; + color: #ffffff !important; } } @@ -3152,7 +3184,7 @@ input:focus-visible { border-width: 0px 0px 1px 0px !important; .text-muted { - color: #fff !important; + color: #ffffff !important; } } @@ -3168,11 +3200,6 @@ input:focus-visible { filter: brightness(0) invert(1); } - .launch-btn { - filter: brightness(0.4) !important; - background: #8d9095; - } - .badge { .svg-icon { filter: brightness(1) invert(0); @@ -3183,7 +3210,7 @@ input:focus-visible { background: transparent; .text-muted { - color: #fff !important; + color: #ffffff !important; } } @@ -3192,15 +3219,25 @@ input:focus-visible { border-width: 0px 0px 1px 0px !important; } + .home-page-content { + .hr-text { + color: var(--slate11) !important; + text-transform: lowercase !important; + font-weight: 400; + font-size: 12px; + line-height: 20px; + } + } + .hr-text { - color: #fff !important; + color: #ffffff !important; } .skeleton-line::after { background-image: linear-gradient(to right, - #566177 0, - #5a6170 40%, - #4c5b79 80%); + #121212 0, + #121212 40%, + #121212 80%); } .app-icon-skeleton::after { @@ -3224,7 +3261,7 @@ input:focus-visible { } .select-search__select { - background: #fff; + background: #ffffff; box-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.15); } @@ -3234,7 +3271,7 @@ input:focus-visible { .select-search__option, .select-search__not-found { - background: #fff; + background: #ffffff; } .select-search__option.is-highlighted, @@ -3245,21 +3282,21 @@ input:focus-visible { .select-search__option.is-highlighted.is-selected, .select-search__option.is-selected:hover { background: #2eb378; - color: #fff; + color: #ffffff; } .org-users-page { .user-email, .user-status { - filter: brightness(0) invert(1); + color: var(--slate12) !important; } } .org-users-page { .select-search__option.is-selected { background: $primary; - color: $white; + color: #ffffff; } .select-search__option:not(.is-selected):hover { @@ -3282,7 +3319,7 @@ input:focus-visible { .org-variables-page { .select-search__option.is-selected { background: $primary; - color: $white; + color: #ffffff; } .select-search__option:not(.is-selected):hover { @@ -3320,8 +3357,8 @@ input:focus-visible { .select-search:not(.is-loading):not(.select-search--multiple) .select-search__value::after { transform: rotate(45deg); - border-right: 1px solid #fff; - border-bottom: 1px solid #fff; + border-right: 1px solid #ffffff; + border-bottom: 1px solid #ffffff; } .widget-documentation-link { @@ -3383,7 +3420,7 @@ input:focus-visible { .theme-dark .markdown>table thead th, .theme-dark .table thead th { background: #1c252f; - color: #fff; + color: #ffffff; } .sketch-picker { @@ -3470,7 +3507,7 @@ input[type="text"] { --rmsc-border: #333333; --rmsc-gray: #555555; --rmsc-bg: #1f2936; - color: #fff; + color: #ffffff; } } @@ -3587,7 +3624,7 @@ input[type="text"] { } .nav-item { - background: #fff; + background: #ffffff; font-size: 14px; font-style: normal; font-weight: 400; @@ -3607,7 +3644,7 @@ input[type="text"] { .nav-tabs .nav-link.active { font-weight: 400 !important; - color: $primary !important; + color: $primary !important; } .empty { @@ -3664,7 +3701,7 @@ input[type="text"] { line-height: 40px; margin-bottom: 16px; margin-top: 40px; - color: #000; + color: var(--slate12); font-family: Inter; } @@ -3679,7 +3716,7 @@ input[type="text"] { line-height: 20px; display: flex; align-items: center; - color: #687076; + color: var(--slate11) !important; } // template card styles @@ -3903,14 +3940,8 @@ input[type="text"] { .jet-listview { overflow-y: overlay; overflow-x: hidden; - - - // .rows { - // } - - // .list-item { - // } } + .jet-listview::-webkit-scrollbar-track { background: transparent; @@ -4099,7 +4130,7 @@ input[type="text"] { [data-rb-event-key="close-inpector-light"] { position: absolute; right: -80px; - background-color: #fff !important; + background-color: #ffffff !important; width: 10% !important; } @@ -4128,7 +4159,7 @@ input[type="text"] { .tabs-inspector.dark { .nav-link.active { - border-bottom: 1px solid $primary !important; + border-bottom: 1px solid $primary !important; } } @@ -4175,7 +4206,7 @@ input[type="text"] { } .bg-primary-lt { - color: #fff !important; + color: #ffffff !important; background: #6383db !important; } @@ -4308,6 +4339,13 @@ input[type="text"] { input { width: 200px; border-radius: 5px !important; + color: var(--slate12); + background-color: var(--base); + } + + .input-icon .form-control:not(:first-child), + .input-icon .form-select:not(:last-child) { + padding-left: 28px !important; } input:focus { @@ -4321,8 +4359,19 @@ input[type="text"] { .input-icon .input-icon-addon.end { pointer-events: auto; + .tj-common-search-input-clear-icon { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px; + width: 20px; + height: 20px; + background: var(--indigo3) !important; + border-radius: 4px; + } + div { - background-color: #a6b6cc; border-radius: 12px; color: #ffffff; padding: 1px; @@ -4344,7 +4393,7 @@ input[type="text"] { } input { - border-radius: $border-radius !important; + border-radius: $border-radius !important; padding-left: 1.75rem !important; } } @@ -4363,7 +4412,6 @@ input[type="text"] { * Folder List */ .folder-list { - color: #292d37; overflow-y: auto; .list-group-transparent .list-group-item.active { @@ -4385,29 +4433,47 @@ input[type="text"] { } .list-group-item.all-apps-link { - font-weight: 400; + display: flex; + align-items: center; + color: var(--slate12); + border-radius: 6px; + + &:active { + background: var(--indigo4); + } + + &:focus { + box-shadow: 0px 0px 0px 4px #DFE3E6; + } } .folder-info { - color: #8991a0; - font-size: 0.75rem; display: contents; + font-weight: 500 !important; + display: flex; + align-items: center; + letter-spacing: -0.02em; + text-transform: uppercase; + color: var(--slate9); } .folder-create-btn { - color: $primary; + width: 28px; + height: 28px; + background: var(--base); + border: 1px solid; + border-color: var(--slate7); cursor: pointer; + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; } .menu-ico { cursor: pointer; - // padding: 3px; border-radius: 13px; - // &__open { - // background-color: #d2ddec; - // } - img { padding: 0px; height: 14px; @@ -4415,27 +4481,30 @@ input[type="text"] { vertical-align: unset; } } - - // .menu-ico:hover { - // background-color: #d2ddec; - // } } /** * Home page modal */ - .home-modal-backdrop { +.home-modal-backdrop { z-index: 9991; - } +} + .modal-content.home-modal-component { border-radius: 8px; overflow: hidden; - background-color: #fefeff; - color: #000000; + background-color: var(--base); + color: var(--slate12); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + + .modal-header { + border-bottom: 1px solid var(--slate5) !important; + } .modal-header, .modal-body { padding: 16px 28px; + background: var(--base); } .modal-title { @@ -4443,17 +4512,9 @@ input[type="text"] { font-weight: 500; } - // .btn-close { - // width: 3.5rem; - // height: 2.5rem; - // } - - // .modal-body { - // padding-top: 0px; - // } - input { border-radius: 5px !important; + background: var(--base); } .modal-main { @@ -4469,16 +4530,42 @@ input[type="text"] { } } +.home-modal-component-editor.dark { + + .modal-header, + .modal-body { + background-color: #232e3c; + color: #fff; + } + + .form-control { + color: #fff; + background-color: #232e3c !important; + } +} + .onboarding-modal.dark .modal-content { @extend .modal-content.home-modal-component.dark; } +.modal-content.home-modal-component.dark-theme { + .btn-close { + filter: brightness(0) invert(1); + } +} + +.home-modal-component { + .btn-close { + opacity: 1 !important; + } +} + .modal-content.home-modal-component.dark { - background-color: $bg-dark-light !important; - color: $white !important; + background-color: $bg-dark-light !important; + color: $white !important; .modal-header { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; } .btn-close { @@ -4486,22 +4573,22 @@ input[type="text"] { } .form-control { - border-color: $border-grey-dark !important; + border-color: $border-grey-dark !important; color: inherit; } input { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; } .form-select { - background-color: $bg-dark !important; - color: $white !important; - border-color: $border-grey-dark !important; + background-color: $bg-dark !important; + color: $white !important; + border-color: $border-grey-dark !important; } .text-muted { - color: $white !important; + color: $white !important; } } @@ -4538,7 +4625,7 @@ input[type="text"] { visibility: hidden; font-size: 12px; background-color: $black; - color: $white; + color: #ffffff; text-align: center; padding: 5px 10px; position: absolute; @@ -4571,6 +4658,10 @@ input[type="text"] { .icon-change-modal { ul { list-style-type: none; + margin: 0 auto; + text-align: center; + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; li { float: left; @@ -4607,7 +4698,7 @@ input[type="text"] { .animation-fade { animation-name: fade; animation-duration: 0.3s; - animation-timing-function: linear; + animation-timing-function: ease-in; } @keyframes fade { @@ -4722,8 +4813,6 @@ input[type="text"] { position: absolute; width: 12px; height: 12px; - // left: calc(50% - .5rem); - // top: calc(50% - .5rem); animation: spinner-border 0.75s linear infinite; } } @@ -4794,15 +4883,15 @@ div#driver-page-overlay { } .dark-theme-walkthrough#driver-popover-item { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; border-color: rgba(101, 109, 119, 0.16) !important; .driver-popover-title { - color: #fff !important; + color: var(--base) !important; } .driver-popover-tip { - border-color: transparent transparent transparent $bg-dark-light !important; + border-color: transparent transparent transparent $bg-dark-light !important; } .driver-popover-description { @@ -4810,7 +4899,7 @@ div#driver-page-overlay { } .driver-popover-footer .driver-close-btn { - color: #fff !important; + color: #ffffff !important; text-shadow: none !important; } @@ -4834,7 +4923,7 @@ div#driver-page-overlay { .driver-next-btn, .driver-prev-btn { - color: $primary !important; + color: $primary !important; } .driver-disabled { @@ -4855,6 +4944,7 @@ div#driver-page-overlay { background-color: $bg-dark-light; border-color: rgba(101, 109, 119, 0.16); + .popover-body { color: #d9dcde !important; } @@ -4954,6 +5044,12 @@ div#driver-page-overlay { } } +.org-name { + color: var(--slate12) !important; + font-size: 12px; +} + + .organization-list { margin-top: 4px; @@ -5073,13 +5169,82 @@ div#driver-page-overlay { } } +.sso-button-footer-wrap { + display: flex !important; + justify-content: center; + width: 100%; +} + +.tj-icon { + cursor: pointer; +} + +#login-url, +#redirect-url { + margin-bottom: 0px !important; +} + +.git-encripted-label { + color: var(--green9); +} + +.card-header { + border-bottom: 1px solid var(--slate5) !important; +} + +.manage-sso-container { + position: relative; +} + +.sso-card-wrapper { + background: var(--base); + min-height: 100%; + height: calc(100vh - 156px) !important; + + display: grid; + grid-template-rows: auto 1fr auto; + + .card-header { + border-bottom: 1px solid var(--slate5) !important; + } + + .form-control { + background: var(--base); + } + + .sso-card-footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding: 24px 32px; + gap: 8px; + width: 660px; + height: 88px; + border-top: 1px solid var(--slate5) !important; + background: var(--base); + margin-top: 0px !important; + } +} + // Left Menu .left-menu { - padding: 1rem 0.5rem; - border-radius: 5px; + background: var(--base); + + .tj-list-item { + gap: 40px; + width: 187px; + height: 32px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .folder-list-selected { + background-color: var(--indigo4); + } ul { - overflow: auto; margin: 0px; padding: 0px; @@ -5087,27 +5252,61 @@ div#driver-page-overlay { float: left; list-style: none; width: 100%; - padding: 5px 10px; - font-weight: bold; - border-radius: 5px; + padding: 6px 8px; + border-radius: 6px; cursor: pointer; margin: 3px 0px; + color: var(--base-black) !important; } li.active { background-color: $primary; - color: $white; + color: #ffffff; } li:not(.active):hover { - background-color: #d2daf0; + background: var(--slate4); + border-radius: 6px; } } } +.enabled-tag { + padding: 4px 16px; + gap: 10px; + width: 77px; + height: 28px; + background: var(--grass3); + border-radius: 100px; + color: var(--grass9); + font-weight: 500; +} + +.disabled-tag { + padding: 4px 16px; + gap: 10px; + color: var(--tomato9); + width: 81px; + height: 28px; + background: var(--tomato3); + border-radius: 100px; + font-weight: 500; +} + .manage-sso { .title-with-toggle { width: 100%; + font-weight: 500; + + .card-title { + color: var(--slate12) !important; + font-weight: 500; + } + + .form-check-input { + width: 28px; + height: 16px; + } input[type="checkbox"] { /* Double-sized Checkboxes */ @@ -5129,20 +5328,14 @@ div#driver-page-overlay { overflow: auto; div { - margin: 0px 0px 5px 0px; - background-color: #eaeaea; - float: left; - padding: 2px 10px; - font-size: 11px; - border-radius: 5px; - border: 1px solid #e1e1e1; + color: var(--slate11); + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 20px; } } -.theme-dark .help-text div { - background-color: $dark-background; - border: 1px solid $dark-background; -} .org-invite-or { padding: 1rem 0rem; @@ -5156,7 +5349,7 @@ div#driver-page-overlay { } h2 span { - background: #fff; + background: #ffffff; padding: 0 10px; } } @@ -5179,7 +5372,7 @@ div#driver-page-overlay { } .selected-node { - border-color: $primary-light !important; + border-color: $primary-light !important; } .json-tree-icon-container .selected-node>svg:first-child { @@ -5269,7 +5462,7 @@ div#driver-page-overlay { } .selected-node { - border-color: $primary-light !important; + border-color: $primary-light !important; } .selected-node .group-object-container .badge { @@ -5408,7 +5601,7 @@ div#driver-page-overlay { font-size: 12px; border-radius: 4px; color: rgba(0, 0, 0, .65); - background-color: #fff; + background-color: #ffffff; border: 1px solid #d9d9d9; min-width: 40px; width: auto !important; @@ -5536,7 +5729,7 @@ div#driver-page-overlay { //Kanban board .kanban-container.dark-themed { - background-color: $bg-dark-light !important; + background-color: $bg-dark-light !important; .kanban-column { .card-header { @@ -5582,7 +5775,7 @@ div#driver-page-overlay { } .dnd-card.card.card-dark { - background-color: $bg-dark !important; + background-color: $bg-dark !important; } } @@ -5781,7 +5974,7 @@ a.step-item-disabled { } .step-item.active:before { - background: #fff !important; + background: #ffffff !important; } .steps .step-item.active:before { @@ -5815,6 +6008,7 @@ a.step-item-disabled { .notification-center { max-height: 500px; overflow: auto; + margin-left: 11px !important; .empty { padding: 0 !important; @@ -5826,6 +6020,14 @@ a.step-item-disabled { .card { min-width: 400px; + background: var(--base); + color: var(--slate12); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + } + + .card-footer { + background: var(--base); + color: var(--slate12); } .spinner { @@ -5833,8 +6035,6 @@ a.step-item-disabled { } } -// steps-widget end - // profile-settings css .confirm-input { padding-right: 8px !important; @@ -5891,7 +6091,7 @@ input.hide-input-arrows { .icon-box { padding: 7px 5px 7px 2px; - color: #fff; + color: #ffffff; .icon { stroke-width: 4.5px; @@ -5899,10 +6099,10 @@ input.hide-input-arrows { } .tick-box { - border: 3px solid $primary; + border: 3px solid var(--indigo9); .icon-box { - background: $primary; + background: var(--indigo9); } } @@ -5920,13 +6120,12 @@ input.hide-input-arrows { .popover-header { background-color: #232e3c; border-bottom: 1px solid #324156; - ; } } .popover-header { padding-bottom: 0; - background-color: #fff; + background-color: #ffffff; .input-icon { margin-bottom: 0.5rem !important; @@ -6010,8 +6209,8 @@ input.hide-input-arrows { } .list-group-item.active { - background-color: #edf1ff; - color: #4d72fa; + background-color: var(--indigo4); + color: var(--slate12); font-weight: 600; margin-top: 0px; } @@ -6217,26 +6416,51 @@ input.hide-input-arrows { } } -.user-group-container-wrap, -.workspace-variable-container-wrap { - margin-top: 1.25rem; +.user-group-container-wrap { + margin: 20px auto 0 auto; } .dragged-column { z-index: 1001; } +#storage-sort-popover { + max-width: 800px; + width: 800px; + background-color: var(--base); + box-sizing: border-box; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 4px; + border: 1px solid var(--slate3) !important; + left: 109px !important; + top: 8px !important; + position: absolute !important; + + + .card-body, + .card-footer { + background: var(--base); + } +} + + #storage-filter-popover { max-width: 800px; width: 800px; - background-color: $white; + background-color: var(--base); box-sizing: border-box; - box-shadow: 0px 2px 4px rgba(40, 57, 72, 0.06), 0px 4px 6px rgba(40, 57, 72, 0.1); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); border-radius: 4px; -} + border: 1px solid var(--slate3) !important; + left: 193px !important; + top: 10px !important; + position: absolute !important; -#storage-filter-popover.theme-dark { - background-color: #22272E; + + .card-body, + .card-footer { + background: var(--base); + } } // Table set to full width @@ -6418,7 +6642,7 @@ tbody { display: flex; justify-content: flex-start; padding: 0.75rem 0.25rem; - border: 1px solid $border-grey-light; + border: 1px solid var(--slate7); } } @@ -6432,12 +6656,12 @@ tbody { } .dropdown-table-column-hide { - background-color: #fff; + background-color: #ffffff; box-shadow: 0 0 0 2px #0000001a; } .dropdown-table-column-hide-dark-themed { - color: #fff !important; + color: #ffffff !important; background-color: #1f2936 !important; box-shadow: 0 0 0 2px #9292921a; } @@ -6491,9 +6715,9 @@ tbody { } .add-more-columns-btn { - background: #F0F4FF; + background: var(--indigo3); font-weight: 500; - color: #3E63DD; + color: var(--indigo9); font-size: 12px; border-radius: 600; } @@ -6702,6 +6926,10 @@ tbody { } } +.subheader { + margin-bottom: 12px; +} + .theme-dark { .layout-sidebar-icon { &:hover { @@ -6710,14 +6938,10 @@ tbody { } .tooljet-database { - .btn { - background-color: #273342; - color: #fff; - } .table-name, .subheader { - color: #fff; + color: var(--slate9); } .list-group-item.active { @@ -6772,25 +6996,6 @@ tbody { background-color: #1F2936; } } - - .app-icon-main { - background: #2F3C4C !important; - } - - .launch-button { - svg { - path { - fill: white !important; - } - } - } - - .breadcrumb-item { - a { - - color: white !important; - } - } } :root { @@ -6801,8 +7006,6 @@ tbody { .application-brand { position: relative; display: flex; - padding: 6px; - margin-bottom: 6px; justify-content: center; } @@ -6812,68 +7015,114 @@ tbody { } .app-icon-main { - background: #F0F4FF; + background: var(--indigo3) !important; + border-radius: 6px !important; + display: flex; + justify-content: center; + align-items: center; + width: 48px; + height: 48px; } .user-avatar-nav-item, .audit-log-nav-item, .notification-center-nav-item { border-radius: 4px; - position: fixed; - bottom: 8px; - left: 8px; } .audit-log-nav-item { bottom: 40px; } -.notification-center-nav-item { - bottom: 60px; +.workspace-content-wrapper, +.database-page-content-wrap { + background: var(--slate2); } +.workspace-variable-table-card { + margin: 0 auto; + width: 880px; +} .organization-page-sidebar { - height: calc(100vh - 48px); + height: calc(100vh - 64px); max-width: 288px; + background-color: var(--base); + border-right: 1px solid var(--slate5) !important; + display: grid !important; + grid-template-rows: auto 1fr auto !important; } .home-page-sidebar { max-width: 288px; + background-color: var(--base); + border-right: 1px solid var(--slate5); + display: grid; + grid-template-rows: auto 1fr auto; +} +.empty-home-page-image { + margin-top: 14px; +} + +.create-new-table-btn { + width: 248px; + + button { + height: 40px !important; + + } } .tooljet-database-sidebar { max-width: 288px; + background: var(--base); + border-right: 1px solid var(--slate5); + .sidebar-container { - padding: 24px; - height: 124px !important; + height: 40px !important; + padding-top: 1px !important; + margin: 0 auto; + display: flex; + justify-content: center; } } -.all-apps-link { - font-weight: 400; - font-size: 14px; - height: 32px; +.create-new-app-dropdown { + width: 248px !important; + + + .dropdown-toggle-split { + border-left: 1px solid var(--indigo11) !important; + } + + button { + background-color: var(--indigo9) !important; + } } .create-new-app-button { - // max-width: 195px; font-weight: 500; font-size: 14px; - height: 32px; + height: 40px; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; } .create-new-app-button+.dropdown-toggle { - height: 32px; + height: 40px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; } .custom-select { .select-search-dark__value::after { content: none; } - .select-search-dark__select,.select-search__select{ + + .select-search-dark__select, + .select-search__select { min-width: fit-content; max-width: 100% !important; } @@ -7132,7 +7381,10 @@ tbody { } .app-creation-time { - font-size: 12px; + color: var(--slate11) !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .font-weight-400 { @@ -7163,24 +7415,10 @@ tbody { .layout-header { position: fixed; right: 0; - left: 48px; + left: 56px; z-index: 1; - background: #ffff; -} - -.theme-dark { - .layout-header { - background: #1f2936 !important; - } -} - -.organization-selector { - max-width: 288px; -} - -.organization-avatar { - max-width: 34px; - margin-right: 10px; + background: var(--base); + height: 64px; } .layout-sidebar-icon { @@ -7193,25 +7431,20 @@ tbody { } } -.folder-menu-icon { - display: none; -} -.organization-selector { +.tj-dashboard-section-header { max-width: 288px; - max-height: 48px; - padding: 8px 16px; -} - -.organization-avatar { - max-width: 34px; - margin-right: 10px; + max-height: 64px; + padding-top: 20px; + padding-left: 20px; + padding-bottom: 24px; + border-right: 1px solid var(--slate5); } .layout-sidebar-icon { &:hover { background: #ECEEF0; - border-radius: 6px; + border-radius: 4px; } &:focus { @@ -7220,13 +7453,28 @@ tbody { } .folder-menu-icon { - display: none; + visibility: hidden !important; } .folder-list-group-item:hover .folder-menu-icon { - display: block; + visibility: visible !important; } +.folder-list-group-item { + &:hover { + background: #ECEEF0; + } + + &:active { + background: var(--indigo4); + } + + &:focus { + box-shadow: 0px 0px 0px 4px #DFE3E6; + } +} + + .app-versions-selector { display: inline-flex; align-items: center; @@ -7250,23 +7498,22 @@ tbody { line-height: 20px; } -.app-version-delete { - display: none; -} .custom-version-selector__option:hover .app-version-delete { display: block; } .editor .editor-sidebar { - border-top: 1px solid #E6E8EB; + border-top: 1px solid var(--slate7); } .editor .navbar-brand { - border-right: 1px solid #E6E8EB; - padding-right: 12px !important; - padding-left: 4px; + border-right: 1px solid var(--slate7); padding-bottom: 1rem; + border-right: 1px solid var(--slate7); + width: 48px; + display: flex; + justify-content: center; } .theme-dark { @@ -7280,7 +7527,6 @@ tbody { } .modal-backdrop { - // background-color: hsla(0, 0%, 0%, 0.439); opacity: 0.5; } @@ -7299,7 +7545,12 @@ tbody { position: fixed; bottom: 0px; right: 0; - left: 336px; //48+288 + left: 344px; +} + +.home-page-footer { + height: 52px; + background-color: var(--base) !important; } .pagination-container { @@ -7315,9 +7566,30 @@ tbody { } } -.profile-card.dark.card { - background-color: #1F2936; - color: #C0C8CC; +.profile-card { + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 6px; + padding: 4px 0px; + width: 84px; + height: 86px; + margin-left: 10px; + background-color: var(--base); + + .dropdown-item { + width: 84px; + height: 36px; + min-width: 84px !important; + } + + svg { + margin-left: 2px; + } + + a { + span { + margin-left: 4px; + } + } } .theme-dark { @@ -7368,20 +7640,24 @@ tbody { // DASHBOARD SCROLL STYLES---> .create-new-app-wrapper { - height: 64px; + margin: 0 auto; + display: flex; + justify-content: center; + padding-top: 4px; } .home-page-sidebar { - height: calc(100vh - 48px) !important; -} + height: calc(100vh - 64px) !important; //64 is navbar height -.folder-list { - height: calc(100vh - 112px) !important; // 48px [navbar] + 64px [ all apps ] + .folder-list-user { + height: calc(100vh - 116px) !important; //64 is navbar height + 52 px footer + } } .home-page-content { - height: calc(100vh - 48px) !important; - overflow-y: scroll; + height: calc(100vh - 64px) !important; + overflow-y: auto; + background: var(--slate2); } .application-folders-list { @@ -7392,20 +7668,77 @@ tbody { // TABLE .table-left-sidebar { - height: calc(100vh - 173px) !important; // 48px [navbar] + 124px [ add table and search ] + extra 1 px + height: calc(100vh - 104px) !important; // 62px [navbar] + 40px [ add table and search ] + extra 2 px(border) overflow-y: auto; } -.database-table-header-wrapper { - height: 96px !important; -} - .toojet-db-table-footer { height: 52px; + background: var(--base) !important; +} + +.home-app-card-header { + margin-bottom: 32px; } .homepage-app-card { - height: 188px; + height: 166px; + outline: 1px solid var(--slate3); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; + padding: 16px; + background-color: var(--base) !important; + + .appcard-buttons-wrap { + display: none; + } + + .home-app-card-header { + .menu-ico { + visibility: hidden !important; + } + } + + &:hover { + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + + .home-app-card-header { + margin-bottom: 12px; + + .menu-ico { + visibility: visible !important; + } + } + + .app-creation-time-container { + margin-bottom: 0px; + } + + .app-card-name { + margin-bottom: 0px; + } + + .app-creation-time { + display: none; + } + + + .appcard-buttons-wrap { + display: flex; + padding: 0px; + gap: 12px; + width: 240px; + height: 28px; + flex-direction: row; + + } + + .app-icon-main { + width: 36px; + height: 36px; + + } + } } .app-creation-time-container { @@ -7448,9 +7781,13 @@ tbody { } } -.table-list-items#popover-contained:has(.theme-dark) { - border-color: inherit; - overflow: hidden; +.table-list-items#popover-contained { + .popover-body { + outline: 1px solid var(--slate3); + background: var(--base); + overflow: hidden; + } + } .table-list-item-popover.dark { @@ -7469,24 +7806,24 @@ tbody { } @keyframes up-and-down { - to{ - opacity: 0.2; - transform: translateY(-20px); - + to { + opacity: 0.2; + transform: translateY(-20px); + } } -.spin-loader { +.spin-loader { position: fixed; width: 100%; - - .load{ + + .load { display: flex; justify-content: center; margin: 200px auto; } - .load div{ + .load div { width: 20px; height: 20px; background-color: #3E63DD; @@ -7498,11 +7835,11 @@ tbody { animation-direction: alternate; } - .load .two{ + .load .two { animation-delay: 0.3s; } - .load .three{ + .load .three { animation-delay: 0.6s; } } @@ -7510,13 +7847,13 @@ tbody { .organization-switch-modal { font-family: 'IBM Plex Sans'; - .modal-dialog{ + .modal-dialog { width: 376px; } - .modal-content{ + .modal-content { background: linear-gradient(0deg, #FFFFFF, #FFFFFF), - linear-gradient(0deg, #DFE3E6, #DFE3E6); + linear-gradient(0deg, #DFE3E6, #DFE3E6); } .modal-header { @@ -7537,7 +7874,7 @@ tbody { font-weight: 400; font-size: 14px; line-height: 20px; - color:#687076; + color: #687076; text-align: Center; margin-bottom: 0px; } @@ -7545,12 +7882,12 @@ tbody { .modal-body { padding: 18px 32px; - + .org-list { display: flex; flex-direction: column; - .org-item{ + .org-item { height: 50px; display: flex; align-items: center; @@ -7603,9 +7940,11 @@ tbody { } .organization-switch-modal.dark-mode { + .modal-footer, .modal-header { border-color: #232e3c !important; + p { color: rgba(255, 255, 255, 0.5) !important; } @@ -7624,8 +7963,8 @@ tbody { } - .modal-body { - .org-list{ + .modal-body { + .org-list { span { color: white; } @@ -7636,6 +7975,7 @@ tbody { } } } + .datasources-category { color: var(--slate10); } @@ -7643,15 +7983,1532 @@ tbody { .react-tooltip { font-size: .765625rem !important; } + +.add-new-workspace-icon-wrap { + display: flex; + flex-direction: row; + align-items: center; + padding: 8px; + width: 34px; + height: 34px; + background: var(--indigo3); + border-radius: 6px; +} + +.add-new-workspace-icon-old-wrap { + display: none; +} + +.add-workspace-button { + padding: 8px 12px; + gap: 11px; + height: 50px; + + &:hover { + background: var(--indigo3); + margin: 0 auto; + border-radius: 6px; + padding-bottom: 10px; + + .add-new-workspace-icon-old-wrap { + padding: 8px; + width: 34px; + height: 34px; + background: var(--indigo9); + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; + + } + + .add-new-workspace-icon-wrap { + display: none; + + } + } + +} + +.tj-folder-list { + display: flex; + align-items: center; + color: var(—-slate12) !important; +} + +.app-card-name { + color: var(—-slate12); + margin-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dashboard-breadcrumb-header { + display: flex; + align-items: center; +} + +.tj-version { + margin-right: 44px; + display: flex; + align-items: center; + color: var(--slate9); + +} + +.folder-list { + color: var(—-slate9) !important; +} + +.tj-folder-header { + margin-bottom: 12px; + height: 37px; + cursor: pointer; +} + +.tj-dashboard-header-title-wrap { + display: flex; + justify-content: center; + align-items: center; + color: var(--slate11); + + a { + text-decoration: none; + } +} + +.theme-dark { + .tj-onboarding-phone-input-wrapper { + .flag-dropdown { + background-color: #1f2936 !important; + + .country-list { + background-color: #1f2936 !important; + background: #1f2936; + + li { + .country .highlight { + background-color: #3a3f42; + color: #000 !important; + + div { + .country-name { + color: #6b6b6b !important; + } + } + + } + + &:hover { + background-color: #2b2f31; + } + + } + } + } + + } + + .react-tel-input .country-list .country.highlight { + color: #6b6b6b; + } +} + +.dashboard-breadcrumb-header-name { + font-weight: 500 !important; + color: var(—-slate12) !important; +} + +.tj-dashboard-header-wrap { + padding-top: 22px; + padding-bottom: 22px; + padding-left: 40px; + height: 64px; + border-bottom: 1px solid var(--slate5); +} + +.dashboard-breadcrumb-header-name:hover { + text-decoration: none !important; +} + + +.tj-avatar { + border-radius: 6px; + width: 36px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--slate3) !important; + color: var(--slate11) !important; + text-transform: uppercase; + font-weight: 500; + + &:hover { + background-color: var(--slate4); + } + + &:focus { + box-shadow: 0px 0px 0px 4px var(--indigo6); + outline: 0; + } + + &:active { + box-shadow: none; + } +} + +.tj-current-org { + span { + color: var(--slate12); + + } +} + + +.sidebar-inner { + align-items: center; +} + +.workspace-drawer-wrap { + background: var(--base); +} + +.theme-dark { + .drawer-wrap { + background: var(--base); + } +} + +.users-table { + background: var(--base); + padding: 16px; + width: 848px; + margin: 0 auto; + padding: 16px; + + tbody { + + tr>td>span, + tr>td>a { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 140px; + } + } + + thead { + tr { + padding: 0px 6px; + gap: 90px; + width: 848px; + height: 40px; + display: flex; + align-items: center; + margin-top: 6px; + } + + tr>th { + background: var(--base) !important; + border-bottom: none !important; + padding: 0 !important; + width: 282px; + } + } + + tr { + background: var(--base); + height: 66px; + padding: 13px 0px; + border-bottom: 1px solid var(--slate7); + display: flex; + justify-content: space-between; + } + + tr>td { + border-bottom-width: 0px !important; + display: flex; + align-items: center; + flex: 16%; + padding-left: 0px !important; + padding-right: 0px !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.tj-input { + padding: 6px 10px; + gap: 17px; + width: 161.25px; + height: 32px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + + ::placeholder { + color: var(--slate9) !important; + } + +} + +.workspace-setting-buttons-wrap { + display: flex; + gap: 12px; +} + +.workspace-settings-table-wrap { + max-width: 880px; + margin: 0 auto; +} + +.workspace-settings-filters { + display: flex; + gap: 12px; + flex-direction: row; + align-items: center; + position: relative; +} + +.workspace-setting-table-wrapper { + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + outline: 1px solid var(--slate7); + background: var(--base); + width: 880px; + margin: 0 auto; + border-radius: 6px; + height: calc(100vh - 223px); + position: relative; + +} + +.workspace-filter-text { + color: var(--slate11); + margin-bottom: 14px; +} + +.singleuser-btn { + padding: 6px 16px; + gap: 6px; + width: 152px; + height: 32px; + border-radius: 6px; + +} + +.multiuser-btn { + padding: 6px 16px; + gap: 6px; + width: 189px; + height: 32px; + border-radius: 6px; + +} + +.workspace-page-header { + width: 880px; + margin: 0 auto !important; + + div:first-child { + margin: 0 auto !important; + width: 880px; + + } +} + +.workspace-user-archive-btn { + width: 95px; + height: 28px; +} + +.workspace-clear-filter { + margin-left: 8px; + color: var(--indigo9); + font-weight: 600 !important; +} + +.workspace-clear-filter-wrap { + display: flex; + align-items: center; + width: 130px; + justify-content: flex-end; + position: absolute; + right: 16px; +} + +.tj-checkbox { + border-color: var(--slate7); +} + +.workspace-clipboard-wrap { + display: flex; + align-items: center; + width: 162.67px; + cursor: pointer; + + p { + font-weight: 500 !important; + margin-left: 5px; + } + + span { + display: flex; + align-items: center; + } +} + +.workspace-user-status { + margin-right: 22px; + margin-left: 5px; +} + +.worskpace-setting-table-gap { + margin-top: 20px; +} + +.tj-active { + background: #46A758; +} + +.tj-invited { + background: #FFB224; +} + +.tj-archive { + background: #E54D2E; +} + +.liner { + height: 1px; + background: var(--slate5); + width: 880px; + margin-top: 22px; +} + +.edit-button { + width: 135px; + height: 28px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px 16px; + gap: 6px; + width: 135px; + height: 28px; +} + +.launch-button { + display: flex; + width: 93px; + height: 28px; + padding: 4px 16px; + gap: 6px; + align-items: center; + color: var(--slate12); + justify-content: center; +} + +.launch-button.tj-disabled-btn { + cursor: not-allowed; +} + +.breadcrumb-item { + a { + text-decoration: none !important; + color: var(--slate12); + } +} + +.table-list-item { + width: 248px; +} + +.workspace-settings-filter-items { + width: 161.25px; + + .css-13mf2tf-control { + width: 161.25px !important; + + } + + .css-10lvx9i-Input { + margin: 0 !important; + padding: 0 !important; + } + + .css-1bugkci-control, + .css-42vs31, + .css-ob45yj-menu { + background-color: var(--base) !important; + width: 161.25px !important; + } + + .css-6t9fnh-control { + border: 1px solid var(--slate7) !important; + background: var(--base); + color: var(--slate9); + width: 161.25px; + height: 32px; + + .css-1opnhvy-singleValue { + color: var(--slate9) !important; + + } + } + + input.tj-checkbox { + background: var(--base) !important; + color: var(--slate9); + border: 1px solid var(--slate7) !important; + + ::placeholder { + color: var(--slate9); + } + } +} + + +.tj-db-dataype { + margin-left: 8px; + color: var(--slate11); +} + +.tj-database-column-header { + color: var(--slate12); + padding: 4px 4px 4px 8px !important; + text-transform: capitalize !important; + line-height: 0px !important; + font-weight: 500 !important; + font-size: 12px !important; + line-height: 20px !important; + color: var(--slate12) !important; + + &:first-child { + display: flex !important; + align-items: center !important; + padding-left: 1rem !important; + } + +} + +.tj-database-column-row { + margin: 0; + + th:first-child { + height: 28px; + } + + th:first-child>div { + height: 16px; + width: 16px; + display: flex; + align-items: center; + height: 28px; + + input { + border-radius: 4px; + } + + } +} + +.tj-db-operaions-header { + height: 48px; + padding: 0 !important; + display: flex; + align-items: center; + background-color: var(--base); + + .row { + margin-left: 0px; + } + + .col { + padding-left: 0px; + display: flex; + gap: 8px; + align-items: center; + } +} + +.add-new-column-btn { + margin-left: 16px; + width: 144px !important; + height: 28px; + border-radius: 6px; + padding: 0 !important; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + color: var(--slate12); + border: none; +} + +.tj-db-filter-btn { + width: 81px; + height: 28px; + border-radius: 6px; + background: transparent; + color: var(--slate12); + border: none; + display: flex; + align-items: center; + justify-content: center; +} + +.tj-db-filter-btn-applied, +.tj-db-sort-btn-applied { + display: flex !important; + flex-direction: row !important; + justify-content: center !important; + align-items: center !important; + padding: 4px 16px !important; + width: 171px !important; + height: 28px !important; + background: var(--grass2) !important; + border-radius: 6px !important; +} + +.tj-db-filter-btn-active, +.tj-db-sort-btn-active { + width: 81px !important; + height: 28px !important; + background: var(--indigo4) !important; + border: 1px solid var(--indigo9) !important; + border-radius: 6px !important; + justify-content: center; + color: var(--indigo9) !important; +} + +.tj-db-header-add-new-row-btn { + width: 125px; + height: 28px; + background: var(--indigo3); + border-radius: 6px !important; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 6px; + border: none; + + span { + color: var(--indigo9); + } +} + +.tj-db-sort-btn { + width: 75px; + height: 28px; + background: transparent; + color: var(--slate12); + border: none; + display: flex; + align-items: center; + justify-content: center; +} + +.edit-row-btn { + background: transparent; + color: var(--slate12); + border: none; + display: flex; + align-items: center; +} + +.workspace-variable-header { + width: 880px; + justify-content: end; + margin: 0 auto; + display: flex; + padding: 0; +} + +.add-new-variables-button { + margin-bottom: 20px; + width: 169px; + height: 32px; +} + +.org-users-page-sidebar, +.left-menu { + padding: 16px; + gap: 7px; + width: 220px; + border-right: 1px solid var(--slate5); + overflow-y: auto; + overflow-x: hidden; +} + +.groups-header-wrap { + display: flex; + height: 36px; + border-bottom: 1px solid var(--slate5); +} + +.org-users-page-container { + width: 880px; + margin: 0 auto; +} + +.groups-main-header-wrap { + padding: 20px 0px 8px; + gap: 10px; + width: 612px; + height: 56px; + margin: 0 auto; + display: flex; + justify-content: space-between; + + p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .nav-tabs .nav-link.active { + border-bottom: 2px solid var(--indigo9) !important; + } +} + +.form-check-input:disabled { + background-color: var(--slate8) !important; +} + +.manage-groups-body { + padding: 24px; + font-size: 12px; + overflow-y: auto; + height: calc(100vh - 300px); + +} + +.groups-sub-header-wrap { + width: 612px; + height: 36px; + border-bottom: 1px solid var(--slate5) !important; + + .nav-link.active { + border-bottom: 2px solid var(--indigo9) !important; + border-color: var(--indigo9) !important; + } + + .nav-item { + font-weight: 500 !important; + font-size: 12px !important; + } + + + p { + width: 205px; + } +} + +.groups-btn-container { + width: 880px; + justify-content: space-between; + margin: 0 auto; + margin-bottom: 20px; + height: 32px; + align-items: center; + +} + +.org-users-page { + margin: 0 auto; +} + +.org-users-page-card-wrap { + height: calc(100vh - 208px); +} + +.org-users-page-card-wrap, +.manage-sso-wrapper-card { + display: flex; + flex-direction: row; + background: var(--base); + width: 880px; + outline: 1px solid var(--slate5); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; +} + +.manage-sso-wrapper-card { + margin: 0 auto; + + .card-body { + overflow-y: auto; + padding: 40px; + } + + .card-header { + padding: 0px 24px; + width: 660px; + height: 72px; + border-bottom: 1px solid var(--slate5); + + } + + .form-check { + margin-bottom: 0px !important; + line-height: 24px; + font-size: 16px; + } +} + +.groups-sidebar-nav { + display: flex; + flex-direction: row; + align-items: center; + padding: 6px 8px; + gap: 40px; + width: 188px; + height: 32px; + background: var(--base); + border-radius: 6px; + cursor: pointer; +} + +.org-users-page-card-body { + width: 660px; +} + +.org-users-page { + .nav-tabs .nav-link.active { + background-color: transparent !important; + } + + .nav-tabs .nav-item.show .nav-link, + .nav-tabs .nav-link.active { + border-color: var(--indigo9) !important; + + } + + .nav-link:hover { + border-right: none !important; + border-left: none !important; + border-top: none !important; + + color: var(--indigo9); + } +} + +.groups-selected-row { + background-color: var(--indigo4); +} + +.add-apps-btn { + width: 160px; + height: 32px; +} + +.groups-app-body-header { + border-bottom: 1px solid var(--slate5); + + p { + height: 36px; + display: flex; + align-items: center; + width: 286px; + color: var(--slate11); + + } + + p:first-child { + width: 205px !important; + margin-left: 12px; + } + +} + +.manage-group-tab-icons { + margin-right: 6px; +} + +.manage-groups-no-apps-wrap { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + width: 602px; + + p { + margin-top: 12px; + } + + span { + color: var(--slate11); + margin-top: 4px; + } + + div { + width: 64px; + height: 64px; + background: var(--indigo3); + border-radius: 12px; + display: flex; + justify-content: center; + align-items: center; + margin-top: 88px; + } +} + +.apps-permission-wrap { + height: 72px; + justify-content: center; + gap: 12px; +} + +.apps-folder-permission-wrap, +.apps--variable-permission-wrap { + height: 44px; +} + +.manage-group-permision-header { + border-bottom: 1px solid var(--slate5); + display: flex; + + p { + padding: 8px 12px; + gap: 10px; + width: 206px; + height: 36px; + font-weight: 500; + color: var(--slate11) !important; + } + +} + +.permission-body { + .form-check { + margin-bottom: 0px !important; + } + + tr { + border-bottom: 1px solid var(--slate5); + width: 612px !important; + + } + + td { + font-size: 12px; + font-weight: 500; + line-height: 20px; + letter-spacing: 0em; + text-align: left; + width: 206px !important; + padding-left: 12px; + + div { + padding-left: 12px; + } + } +} + + +.default-option-text { + margin-left: 10px; + margin-right: 16px; + font-size: 11px !important; +} + +.git-sso-help-text { + color: var(--slate11); +} + +.default-group-wrap { + gap: 10px; + width: 119px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--grass3); + border-radius: 100px; +} + +.sso-icon-wrapper { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 8px 8px 8px 16px; + width: 251px; + height: 56px; + background: var(--slate3); + border-radius: 6px; + margin-top: 12px; +} + +.sso-main-box { + justify-content: center; + background: var(--slate6); + padding: 8px 16px; + width: 96px; + height: 40px; + border-radius: 6px; +} + +.default-danger-tag-wrap { + gap: 10px; + width: 113px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: var(--tomato6); + border-radius: 100px; + margin-bottom: 16px; +} + +.manage-group-users-info { + height: 48px; + width: 612px; + border-radius: 6px; + padding: 12px 24px 12px 24px; + background: var(--slate3); + border: 1px solid var(--slate5); + border-radius: 6px; + margin-bottom: 16px; + + p { + color: var(--slate12); + gap: 14px; + display: flex; + align-items: center; + + } +} + +.name-avatar { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + width: 36px; + height: 36px; + background-color: var(--slate3) !important; + border-radius: 6px; + color: var(--slate11); + margin-right: 12px; + text-transform: capitalize; +} + +.manage-group-users-row { + display: flex; + flex-direction: row; + align-items: baseline; + padding: 12px 6px; + width: 612px !important; + height: 64px; + border-bottom: 1px solid var(--slate5); + + p { + width: 272px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + span { + max-width: 150px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + &:hover .apps-remove-btn { + display: flex; + } +} + +.manage-group-app-table-body { + width: 602px !important; + + tr { + display: flex; + font-family: 'IBM Plex Sans'; + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 20px; + color: var(--slate12); + } +} + +.apps-view-edit-wrap { + display: flex; + flex-direction: column; + width: 51px; + margin-right: 32px; +} + +.apps-table-row { + display: grid !important; + grid-template-columns: 205px 286px 115px; + + td { + padding: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &:hover .apps-remove-btn { + display: flex; + } +} + +.apps-remove-btn { + width: 97px; + height: 28px; + font-weight: 600 !important; +} + +.faded-text { + color: var(--slate8); +} + +.manage-groups-app-dropdown { + width: 440px; +} + +.create-new-group-button { + width: 169px; + height: 32px; + border-radius: 6px; +} + +.faded-input { + background: var(--slate5); +} + +.manage-group-table-head { + display: flex; + border-bottom: 1px solid var(--slate5); + width: 612px; + height: 36px; + padding: 8px 12px; + align-items: center; + + + p { + width: 272px !important; + color: var(--slate11); + font-weight: 500; + } + +} + +.manage-groups-permission-apps { + border-bottom: 1px solid var(--slate5); +} + +.manage-groups-permission-apps, +.apps-folder-permission-wrap, +.apps-variable-permission-wrap { + display: flex; + align-items: center; + padding: 12px; + gap: 10px; + + div { + width: 206px; + } +} + +.manage-groups-permission-apps, +.apps-variable-permission-wrap { + gap: 10px; + height: 72px; +} + +.apps-folder-permission-wrap { + height: 44px; + border-bottom: 1px solid var(--slate5); +} + +.delete-group { + text-decoration: none !important; + color: var(--tomato9) !important; +} + +.delete-link, +.remove-decoration { + text-decoration: none !important; +} + +.edit-group { + text-decoration: none !important; + color: var(--slate12) !important; +} + +.removed-decoration { + text-decoration: none !important; +} + +.rmsc .select-item.selected { + color: var(--slate12) !important; + background-color: var(--base) !important; +} + +.manage-groups-app-dropdown { + margin-right: 12px; + + .rmsc .dropdown-container:focus-within { + border: 1px solid var(--indigo9) !important; + box-shadow: 0px 0px 0px 2px #C6D4F9 !important; + } + + .dropdown-heading-value { + span { + color: var(--slate12) !important; + + } + } + + .multi-select { + .dropdown-container { + gap: 17px; + width: 440px; + height: 32px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; + margin-right: 12px; + } + + } + + .dropdown-content { + .panel-content { + background: var(--base); + border: 1px solid var(--slate3); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 6px; + + .select-panel { + .search { + border-bottom: 1px solid var(--slate5); + } + + .search, + input { + background-color: var(--base) !important; + } + } + + input[type='checkbox'] { + border: 1px solid red !important; + } + + .select-item:hover { + background-color: var(--slate3); + } + + + .item-renderer { + span { + font-size: 12px; + color: var(--slate12) + } + } + + } + } +} + +.sso-form-wrap { + .form-label { + font-size: 12px; + font-weight: 500px; + margin-bottom: 4px !important; + color: var(--slate12); + } + + .form-check-label { + font-size: 12px; + font-size: 12px; + line-height: 20px; + color: var(--slate12); + } +} + +.allow-default-sso-helper-text { + white-space: pre-line; +} + +.password-disable-danger-wrap { + padding: 16px; + gap: 16px; + width: 574px; + height: 116px; + background: var(--tomato3); + border: 1px solid var(--tomato5); + border-radius: 6px; +} + +.sso-footer-save-btn { + width: 157px; + height: 40px; +} + +.sso-footer-cancel-btn { + + width: 85px; + height: 40px; +} + +.danger-text-login { + padding-left: 40px !important; +} + +.tick-icon { + width: 20px; + height: 20px; + background: var(--indigo9); + border-radius: 4px; +} + +.invite-user-drawer-wrap { + display: grid; + grid-template-rows: auto 1fr auto; + height: 100vh; +} + +.manage-users-drawer-footer { + padding: 24px 32px; + height: 88px; + border-top: 1px solid var(--slate5) !important; + display: flex; + gap: 8px; + justify-content: end; + + .invite-btn { + width: 140px; + height: 40px; + } + + .cancel-btn { + width: 85px; + height: 40px; + } +} + + +.tj-drawer-tabs-wrap { + display: flex; +} + +.invite-user-drawer-wrap { + .card-header { + flex-direction: column; + display: flex; + justify-content: space-between; + padding: 0px !important; + } + + .card-header-inner-wrap { + justify-content: space-between; + width: 100%; + padding: 16px 20px; + height: 64px; + + } + + .card-header-inner-wrap, + .tj-drawer-tabs-container { + display: flex; + } + + .tj-drawer-tabs-container-outer { + padding-top: 0px; + gap: 10px; + height: 68px; + } + + .tj-drawer-tabs-container { + padding: 2px; + gap: 2px; + + width: 502px; + height: 36px; + background: var(--slate4); + border-radius: 6px; + + } +} + +.tj-drawer-tabs-btn { + padding: 2px 4px; + gap: 6px; + width: 248px; + height: 32px; + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 4px; + border: none; + color: var(--slate11); + display: flex; + align-items: center; + justify-content: center; + background: var(--slate4); + + + span { + margin-left: 4px !important; + font-weight: 500; + + } +} + +.tj-drawer-tabs-btn-active { + background: var(--base); + color: var(--slate12); +} + +.user-number-wrap { + display: flex; + flex-direction: column; + align-items: center; + padding: 8px; + gap: 10px; + width: 36px; + height: 36px; + background: var(--slate3); + border-radius: 1000px; +} + +.user-csv-template-wrap { + display: flex; + padding: 24px; + gap: 14px; + + width: 486px; + height: 152px; + + background: var(--orange3); + + border: 1px solid var(--orange6); + border-radius: 6px; + + div { + display: flex; + flex-direction: column; + + p { + margin-bottom: 12px; + } + + } +} + +.upload-user-form { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 60px 0px; + gap: 36px; + width: 486px; + height: 244px; + border: 2px dashed var(--indigo9); + border-radius: 6px; + align-items: center; + margin: 24px auto; + text-align: center; + + .select-csv-text { + color: var(--indigo9); + margin-bottom: 4px; + } + + span { + color: var(--slate11) !important; + } +} + +.download-template-btn { + width: 184px; + height: 32px; + padding: 0px !important; +} + +.csv-upload-icon-wrap { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 10px; + gap: 10px; + width: 64px; + height: 64px; + background: var(--indigo3); + border-radius: 12px; + margin: 0px auto 12px auto; + cursor: pointer; +} + +.user-csv-template-wrap { + margin-top: 24px; +} + + +.manage-users-drawer-content-bulk { + form { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .manage-users-drawer-content-bulk-download-prompt { + display: flex; + flex-direction: row !important; + justify-content: center; + align-items: flex-start !important; + } +} + + +.manage-users-drawer-content { + margin: 24px 32px; + + div:first-child { + display: flex; + flex-direction: column; + justify-content: center; + align-items: top; + } + + .invite-user-by-email { + display: flex; + } + + .invite-email-body { + width: 452px; + + input { + padding: 6px 10px; + width: 470px; + height: 32px; + color: var(--slate12); + } + } +} + .tj-db-table { overflow-y: auto; height: 110px; - thead th { - position: sticky; - top: 0; - } - table { border-collapse: collapse; width: 100%; @@ -7684,10 +9541,620 @@ tbody { } +.sso-type-header { + margin-left: 10px; +} + +.groups-folder-list { + padding: 6px 8px; + gap: 40px; + max-width: 188px; + height: 32px; + + span { + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + } +} + +.create-group-modal-footer { + display: flex; + align-items: center; + gap: 8px; + justify-content: end; +} + +.add-users-button { + width: 160px; + height: 32px; +} + +.sso-page-inputs { + padding: 6px 10px; + gap: 17px; + width: 612px; + height: 32px; +} + +.workspace-settings-filter-wrap { + background: var(--slate3); + padding: 15px 16px; + gap: 12px; + width: 880px; + height: 62px; + border-right: 1px solid var(--slate7); + border-top: 1px solid var(--slate7); + border-left: 1px solid var(--slate7); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} + + +// users page +.css-1i2tit0-menu { + margin: 0px !important; + background: var(--base); + box-shadow: 0px 4px 6px -2px #10182808 !important; + + .css-2kg7t4-MenuList { + margin: 0px !important; + padding: 0px !important; + background: var(--base); + } +} + +.workspace-settings-nav-items { + padding: 6px 8px; + gap: 40px; + width: 248px; + height: 32px; +} + +.new-app-dropdown { + background: var(--base) !important; + color: var(--slate12); +} + +.workspace-variable-container-wrap { + + .card, + thead { + background: var(--base) !important; + + tr>th, + tbody>tr>td { + background: var(--base) !important; + } + } + +} + +.move-selected-app-to-text { + p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + span { + font-weight: 600; + } + } +} + +.tj-org-dropdown { + .dashboard-org-avatar { + margin-right: 11px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 7px 8px; + gap: 10px; + width: 34px; + height: 34px; + background: var(--slate4) !important; + color: var(--slate9); + border-radius: 6px; + } + + .org-name { + color: var(--slate12) !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.css-1q0xftk-menu { + background-color: var(--base-black) !important; + border: 1px solid hsl(197, 6.8%, 13.6%) !important; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important; + +} + +.css-4yo7x8-menu { + background-color: var(--base) !important; + border: 1px solid var(--slate3) !important; + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important; + border-radius: 6px !important; +} + + +.org-custom-select-header-wrap { + border-bottom: 1px solid var(--slate5); +} + +.btn-close:focus { + box-shadow: none !important; +} + +.template-card { + padding: 16px; + gap: 16px; + width: 272px; + height: 184px; + background: var(--base); + border: 1px solid var(--slate3); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; +} + +.see-all-temlplates-link { + color: var(--indigo9) !important; +} + +.template-card-img { + padding: 0px; + width: 240px; + height: 112px; + border-radius: 4px; +} + +.confirm-dialogue-body { + background: var(--base); + color: var(--slate12); +} + +.folder-header-icons-wrap { + gap: 4px; +} + +.tj-common-search-input { + .input-icon-addon { + padding-right: 8px; + padding-left: 8px; + + } + + input { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + padding: 4px 8px !important; + gap: 16px; + width: 248px !important; + height: 28px !important; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + color: var(--slate12); + padding-left: 33px !important; + + + ::placeholder { + color: var(--slate9); + margin-left: 5px !important; + padding-left: 5px !important; + background-color: red !important; + } + + &:hover { + background: var(--slate2); + border: 1px solid var(--slate8); + } + + &:active { + background: var(--indigo2); + border: 1px solid var(--indigo9); + box-shadow: 0px 0px 0px 2px #C6D4F9; + outline: none; + } + + &:focus-visible { + background: var(--slate2); + border: 1px solid var(--slate8); + border-radius: 6px; + outline: none; + } + + &:disabled { + background: var(--slate3); + border: 1px solid var(--slate7); + } + } + + +} + +.search-icon-wrap { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 7px; + gap: 8px; + width: 28px; + height: 28px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + cursor: pointer; +} + +.sidebar-list-wrap { + margin-top: 24px; + padding: 0px 20px 20px 20px; + height: calc(100vh - 180px); + overflow: auto; + + span { + letter-spacing: -0.02em; + } +} + +.drawer-footer-btn-wrap, +.variable-form-footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding: 24px 32px; + gap: 8px; + height: 88px; + border-top: 1px solid var(--slate5); + background: var(--base); +} + +.drawer-card-title { + padding: 16px; + border-bottom: 1px solid var(--slate5); + + h3 { + margin-bottom: 0px !important; + } +} + +.drawer-card-wrapper, +.variable-form-wrap { + min-height: 100vh; + display: grid; + grid-template-rows: auto 1fr auto; +} + +.add-new-datasource-header-container { + margin-bottom: 24px; + padding-top: 4px; +} + +.folder-list-group-item { + color: var(--slate12) !important; +} + +.table-list-item, +.table-name { + color: var(--slate12) !important; +} + +// targetting all react select dropdowns + +.css-1i2tit0-menu .css-2kg7t4-MenuList { + div { + background-color: var(--base-black); + + &:hover { + background-color: hsl(198, 6.6%, 15.8%); + ; + } + } +} + +.css-ob45yj-menu .css-2kg7t4-MenuList { + div { + background-color: var(--base); + + &:hover { + background-color: var(--slate4); + ; + } + } +} + .selected-ds.row>img { padding: 0 !important; } +.tj-user-table-wrapper { + height: calc(100vh - 270px); //52+64+40+32+20+62 + overflow-y: auto; + background: var(--base); + border-right: 1px solid var(--slate7); + border-bottom: 1px solid var(--slate7); + border-left: 1px solid var(--slate7); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + +} + +.user-filter-search { + padding: 6px 10px; + gap: 16px; + width: 312px; + height: 32px; + background: var(--base); + border: 1px solid var(--slate7); + border-radius: 6px; + + &::placeholder { + color: var(--slate9); + } +} + + + +//TJ APP INPUT +.tj-app-input { + display: flex; + flex-direction: column; + font-family: 'IBM Plex Sans'; + font-style: normal; + + .text-danger { + font-weight: 400 !important; + font-size: 10px !important; + line-height: 16px !important; + color: var(--tomato10) !important; + } + + label { + font-family: 'IBM Plex Sans'; + font-style: normal; + font-weight: 500; + font-size: 12px; + line-height: 20px; + display: flex; + align-items: center; + color: var(--slate12); + margin-bottom: 4px; + } + + input.form-control, + textarea, + .form-control { + gap: 16px !important; + background: var(--base) !important; + border: 1px solid var(--slate7) !important; + border-radius: 6px !important; + margin-bottom: 4px !important; + color: var(--slate12) !important; + + &:hover { + background: var(--slate1) !important; + border: 1px solid var(--slate8) !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + outline: none; + } + + &:focus-visible { + background: var(--indigo2) !important; + border: 1px solid var(--indigo9) !important; + box-shadow: none !important; + } + + } + +} + + + +.tj-sub-helper-text { + font-weight: 400; + font-size: 10px; + line-height: 16px; +} + +.tj-input-success { + color: var(--grass10); +} + +.tj-input-warning { + color: var(--orange10); +} + +.tj-input-helper { + color: var(--slate11); +} + +.tj-input-error { + color: var(--tomato10); +} + +.tj-input-error-state { + border: 1px solid var(--tomato9); +} + +// TJ APP INPUT END + +.search-input-container { + display: flex; +} + +// sidebar styles inside editor :: temporary +.theme-dark, +.dark-theme { + .editor { + .left-sidebar { + background-color: #232e3c !important; + } + } +} + +.tj-db-table { + table { + max-width: calc(100% - 28px); + } + + .datatable { + position: relative; + } +} + +.add-row-btn-database { + position: absolute; + top: 0; + right: -28px; + width: 28px; + height: 28px; + background: var(--slate7); + border-width: 0px 1px 1px 1px; + border-style: solid; + border-color: var(--slate4); + border-radius: 0px !important; +} + +.add-col-btn-database { + position: absolute; + top: 28; + left: 0px; + width: 28px; + height: 28px; + background: var(--slate7); + border-width: 0px 1px 1px 1px; + border-style: solid; + border-color: var(--slate4); + border-radius: 0px !important; +} + +// custom styles for users multiselect in manage users +.manage-groups-users-multiselect { + gap: 17px; + width: 440px; + height: 32px; + background: var(--base); + border-radius: 6px; + + .dropdown-heading { + height: 32px; + padding: 6px 10px; + } + + .dropdown-container { + background: var(--base); + border: 1px solid var(--slate7) !important; + } + + .dropdown-content { + border: 1px solid var(--slate3); + box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + border-radius: 6px; + + .search { + input { + background-color: var(--base); + color: var(--slate12); + } + } + } + + .rmsc, + .dropdown-content, + .panel-content, + .search { + background: var(--base) !important; + } + + .options { + .select-item { + color: var(--slate12); + + &:hover { + background: var(--slate4); + border-radius: 6px; + } + } + } +} + +.select-search__options { + .item-renderer { + display: flex !important; + justify-content: space-between; + padding: 20px; + cursor: pointer; + flex-direction: row; + + div:first-child { + display: flex; + } + + p { + margin-bottom: 0px !important; + color: var(--slate12); + } + + span { + color: var(--slate11); + } + + p, + span { + font-weight: 400; + font-size: 12px; + line-height: 20px; + } + } +} + +.create-new-app-dropdown { + .button:first-child { + padding: 0 !important; + } + + .dropdown-toggle::after { + border: none !important; + content: url("data:image/svg+xml,%3Csvg width='25' height='25' viewBox='0 0 25 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 7.03906C10.5 6.34871 11.0596 5.78906 11.75 5.78906C12.4404 5.78906 13 6.34871 13 7.03906C13 7.72942 12.4404 8.28906 11.75 8.28906C11.0596 8.28906 10.5 7.72942 10.5 7.03906ZM10.5 12.0391C10.5 11.3487 11.0596 10.7891 11.75 10.7891C12.4404 10.7891 13 11.3487 13 12.0391C13 12.7294 12.4404 13.2891 11.75 13.2891C11.0596 13.2891 10.5 12.7294 10.5 12.0391ZM11.75 15.7891C11.0596 15.7891 10.5 16.3487 10.5 17.0391C10.5 17.7294 11.0596 18.2891 11.75 18.2891C12.4404 18.2891 13 17.7294 13 17.0391C13 16.3487 12.4404 15.7891 11.75 15.7891Z' fill='%23fff'/%3E%3C/svg%3E%0A"); + transform: rotate(360deg); + width: 14px; + margin: 0 !important; + display: flex; + align-items: center; + justify-content: center; + padding: 8px 0px 0px 0px; + } +} + +.sso-page-loader-card { + background-color: var(--slate2) !important; + height: 100%; + + .card-header { + background-color: var(--slate2) !important; + } +} + +.workspace-nav-list-wrap { + padding: 4px 20px 20px 20px; + height: calc(100vh - 116px) !important; +} + +.upload-user-form span.file-upload-error { + color: var(--tomato10) !important; +} + .tj-onboarding-phone-input { width: 392px !important; height: 40px; @@ -7706,37 +10173,260 @@ tbody { .tj-onboarding-phone-input-wrapper { margin-bottom: 12px; } + .theme-dark { - .tj-onboarding-phone-input-wrapper { - .flag-dropdown { - background-color: #1f2936 !important; + .tj-onboarding-phone-input-wrapper { + .flag-dropdown { + background-color: #1f2936 !important; - .country-list { - background-color: #1f2936 !important; - background: #1f2936; + .country-list { + background-color: #1f2936 !important; + background: #1f2936; - li { - .country .highlight{ - background-color: #3a3f42; - color: #000 !important; - div{ + li { + .country .highlight { + background-color: #3a3f42; + color: #000 !important; + + div { .country-name { color: #6b6b6b !important; } } - - } - &:hover { - background-color: #2b2f31; - } + } - } - } - } + &:hover { + background-color: #2b2f31; + } + + } + } + } + + } - } .react-tel-input .country-list .country.highlight { color: #6b6b6b; } +} + +.profile-page-content-wrap { + background-color: var(--slate2); + padding-top: 40px; +} + +.profile-page-card { + background-color: var(--base); + border-radius: 6px; +} + +.all-apps-link-cotainer { + border-radius: 6px !important; +} + +.workspace-variable-table-card { + height: calc(100vh - 208px); +} + +.variables-table-wrapper { + tr { + border-width: 0px !important; + } +} + +.home-page-content-container { + max-width: 880px; +} + +@media only screen and (max-width: 1583px) and (min-width: 1312px) { + + .homepage-app-card-list-item { + max-width: 264px; + } + +} + +@media only screen and (min-width: 1728px) { + + .homepage-app-card-list-item { + max-width: 304px; + } + + .home-page-content-container { + max-width: 976px; + } + + .liner { + width: 976px; + } +} + +@media only screen and (max-width: 992px) { + .homepage-app-card-list-item-wrap { + display: flex; + justify-content: center; + margin-left: auto; + margin-right: auto; + width: 100%; + margin-top: 22px; + } + + .homepage-app-card-list-item { + max-width: 304px !important; + flex-basis: 100%; + } +} + +@media only screen and (min-width: 993px) and (max-width: 1311px) { + .home-page-content-container { + max-width: 568px; + } + + .homepage-app-card-list-item-wrap { + row-gap: 20px; + } + + .homepage-app-card-list-item { + max-width: 269px; + flex-basis: 100%; + } + + .liner { + width: 568px; + } +} + +.tj-docs-link { + color: var(--indigo9) !important; + text-decoration: none; + list-style: none; +} + +.datasource-copy-button { + width: 87px; + height: 32px; +} + +.datasource-edit-btn { + height: 27px; + margin-left: 12px; +} + +.datasource-edit-modal { + + .modal-content, + .modal-body, + .modal-header, + .modal-title, + .modal-body-content, + .modal-sidebar, + .card { + background-color: var(--base) !important; + color: var(--slate12) !important; + border-color: var(--slate5) !important; + } + + .datasource-modal-sidebar-footer { + .footer-text { + color: var(--slate12) !important; + } + } + + .form-control-plaintext { + color: var(--slate12) !important; + } + + .card { + &:hover { + background-color: var(--slate2) !important; + } + } +} + +.org-edit-icon { + width: 28px; + height: 28px; + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; + + svg { + height: 14px; + width: 14px; + } +} + +.marketplace-body { + height: calc(100vh - 64px) !important; + overflow-y: auto; + background: var(--slate2); +} + +.plugins-card { + background-color: var(--base); + border: 1px solid var(--slate3); + box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border-radius: 6px; +} + +.template-source-name { + color: var(--slate12) !important; +} + +.marketplace-install { + color: var(--indigo9); +} + +.popover { + .popover-arrow { + display: none; + } +} + +.shareable-link { + .input-group { + .tj-app-input textarea { + width: 600px; + border-radius: 0px !important; + margin-bottom: 0px !important; + background-color: #efefef4d; + color: #545454; + } + } +} + +.confirm-dialogue-modal { + background: var(--base); +} + +.theme-dark { + .icon-widget-popover { + .search-box-wrapper input { + color: #f4f6fa !important; + } + + .search-box-wrapper input:focus { + background-color: #1c252f !important; + } + } + + .shareable-link { + .tj-app-input textarea { + background-color: #5e656e !important; + color: #f4f6fa !important; + border: none !important; + } + } + + .icon-widget-popover { + .search-box-wrapper .input-icon-addon { + min-width: 2.5rem !important; + } + + .search-box-wrapper input { + color: var(--base) !important; + } + } } \ No newline at end of file diff --git a/frontend/src/_styles/typography.scss b/frontend/src/_styles/typography.scss index f93a559530..550146767f 100644 --- a/frontend/src/_styles/typography.scss +++ b/frontend/src/_styles/typography.scss @@ -2,7 +2,7 @@ .tj-text { font-family: 'IBM Plex Sans'; font-style: normal; - color: #121212; + color: var(--slate12) !important; margin: 0; padding: 0; } @@ -87,7 +87,7 @@ .tj-text-md { font-weight: 400; - font-size: 14px; + font-size: 16px; line-height: 24px; margin: 0; padding: 0; @@ -115,13 +115,4 @@ line-height: 16px; margin: 0; padding: 0; -} - - -.tj-para-md { - font-weight: 500; - font-size: 16px; - line-height: 24px; - margin: 0; - padding: 0; } \ No newline at end of file diff --git a/frontend/src/_styles/ui-operations.scss b/frontend/src/_styles/ui-operations.scss new file mode 100644 index 0000000000..604835bbc6 --- /dev/null +++ b/frontend/src/_styles/ui-operations.scss @@ -0,0 +1,38 @@ +.ghost-black-operation { + color: var(--slate12); + border: none; + background: transparent; + + &:hover { + color: var(--slate11); + } + + &:active { + color: var(--indigo9); + } + + &:focus-visible { + color: var(--slate11); + background: var(--base); + border: none; + box-shadow: 0px 0px 0px 4px var(--slate6); + outline: none; + } + + &.applied { + background: var(--grass12); + border-radius: 6px; + } + + &.open { + background: var(--indigo4); + border: 1px solid var(--indigo9); + border-radius: 6px; + color: var(--indigo9) !important; + } + + &.disabled { + background: var(--slate3); + border-radius: 6px; + } +} \ No newline at end of file diff --git a/frontend/src/_styles/widgets/multi-select.scss b/frontend/src/_styles/widgets/multi-select.scss index 374d8f7f50..a745bd543f 100644 --- a/frontend/src/_styles/widgets/multi-select.scss +++ b/frontend/src/_styles/widgets/multi-select.scss @@ -1,149 +1,229 @@ @import "../colors.scss"; .tj-ms { - position: relative; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - border: 1px solid #dadcde; - border-radius: 4px; + position: relative; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + padding: 6px 10px; + gap: 17px; + width: 440px; + height: 32px; + background: var(--base) !important; + border: 1px solid var(--slate7); + border-radius: 6px !important; + display: flex; + align-items: center; - .select-search__select, .select-search-dark__select{ - display: none; - } - - .has-focus>.select-search__select, - .has-focus>.select-search-dark__select { - display: block; + + .select-search__select { + top: 34px; + } + + .select-search__select, + .select-search-dark { + background-color: var(--base) !important; + box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14); + + &:focus-visible { + background: #F8FAFF; + border: 1px solid var(--indigo9); } + } - .select-search-dark.is-loading .select-search-dark__value::after { - background-size: 10px; - } - - .select-search-dark__value::after { - right: 13px; - width: 10px; - height: 10px; - } + .select-search__value { + background: var(--base); + } - .select-search-dark:not(.is-loading) .select-search-dark__value::after { - right: 13px; - width: 6px; - height: 6px; - } + .select-search__row { + border-top: none !important; + } - .select-search.is-loading .select-search__value::after { - background-size: 10px; - } + .select-search__option, + .select-search__not-found { + background-color: var(--base) !important; + color: var(--slate12); + border: 1px solid var(--slate5); + box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14); + border-radius: 6px; + margin: 0 auto; + } - .select-search__value::after { - top: calc(50% - 4px); - right: 8px; - width: 10px; - height: 10px; - } - .select-search:not(.is-loading) .select-search__value::after { - right: 13px; - width: 6px; - height: 6px; - } + .select-search__input { + padding: 0 !important; + color: var(--slate9) !important; + background: var(--base) !important; + } - &:hover { - border-color: rgba(66, 153, 225, 0.1) - } - - .tj-ms-preview { - padding: 6px 3px; + .select-search__select, + .select-search-dark__select { + display: none; + } - .count-main { - padding: 1px 3px 1px 7px; - border: 1px solid #eaeaea; - border-radius: 5px; - background-color: #f0f2f6; - - .selected-count { - padding-right: 5px; - font-size: .8rem; - line-height: 20px; - white-space: nowrap; - } - - .select-close-btn { - color: #9a9191; - cursor: pointer; - } - } - - .count-main:hover { - border: 1px solid #d1cccc; - } - - svg { - width: 1rem; - } - } - - .select-search, - .select-search-dark { - position: unset; - - input { - border: none; - text-overflow: ellipsis; + .has-focus>.select-search__select, + .has-focus>.select-search-dark__select { + display: block; + } + + + .select-search-dark.is-loading .select-search-dark__value::after { + background-size: 10px; + } + + .select-search-dark__value::after { + right: 13px; + width: 10px; + height: 10px; + } + + .select-search-dark:not(.is-loading) .select-search-dark__value::after { + right: 13px; + width: 6px; + height: 6px; + } + + .select-search.is-loading .select-search__value::after { + background-size: 10px; + } + + .select-search__value::after { + top: calc(50% - 4px); + right: 8px; + width: 10px; + height: 10px; + } + + .select-search:not(.is-loading) .select-search__value::after { + right: 13px; + width: 6px; + height: 6px; + } + + &:hover { + border-color: rgba(66, 153, 225, 0.1) + } + + .tj-ms-preview { + padding: 3px 3px; + + .count-main { + padding: 1px 3px 1px 7px; + border: 1px solid var(--slate5); + border-radius: 5px; + background-color: var(--slate8); + color: var(--base); + + .selected-count { + padding-right: 5px; + font-size: .8rem; + line-height: 20px; white-space: nowrap; - overflow: hidden; + } + + .select-close-btn { + color: #9a9191; + cursor: pointer; + } + } + + .count-main:hover { + border: 1px solid #d1cccc; + } + + svg { + width: 1rem; + } + } + + .select-search, + .select-search-dark { + position: unset; + + input { + border: none; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + + .select-search-dark__input { + background-color: var(--base); + border: none; + } + + .select-search-dark__select { + background-color: var(--base); + padding: 14px; + border: 1px solid var(--slate5); + box-shadow: 0px 32px 64px -12px rgba(16, 24, 40, 0.14); + border-radius: 6px; + top: 34px !important; + + ul { + width: 100%; + + + li { + .item-renderer { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 60px; + + + div { + display: flex; + } + } + } } } - - .tj-ms-count { - border-radius: 2px; +} + +.tj-ms-count { + border-radius: 2px; + display: flex; + + .tj-ms-preview { + z-index: 2; display: flex; - - .tj-ms-preview { - z-index: 2; - padding: 6px; - } + align-items: center; } - - .theme-dark .tj-ms { - background-color: #273342; - border: 1px solid #232e3c; - - &:hover { - border-color: $white; - } - - .tj-ms-count { - color: $white; - } - - .tj-ms-preview { - color: #273342; - } +} + +.tj-ms-count { + &:hover { + border: 1px solid var(--indigo9) !important; } - - .selected-section { - overflow: auto; - - .tj-ms { - border: none; - background-color: unset; - float: left; - } - - .selected-heading { - padding: 1px 7px 1px 7px; - margin: 6px 2px 6px 5px; - border-radius: 6px; - font-weight: 500; - float: left; - } - - .selected-text { - line-height: 24px; - font-weight: 300; - margin: 6px 0px; - white-space: nowrap; - float: left; - } - } \ No newline at end of file + + &:focus-visible { + border: 1px solid var(--indigo9); + } +} + +.selected-section { + overflow: auto; + + .tj-ms { + border: none; + background-color: unset; + float: left; + } + + .selected-heading { + padding: 1px 7px 1px 7px; + margin: 6px 2px 6px 5px; + border-radius: 6px; + font-weight: 500; + float: left; + } + + .selected-text { + line-height: 24px; + font-weight: 300; + margin: 6px 0px; + white-space: nowrap; + float: left; + } +} \ No newline at end of file diff --git a/frontend/src/_ui/AlertDialog/index.jsx b/frontend/src/_ui/AlertDialog/index.jsx index c910d1c3c3..ec73e79a1d 100644 --- a/frontend/src/_ui/AlertDialog/index.jsx +++ b/frontend/src/_ui/AlertDialog/index.jsx @@ -2,12 +2,29 @@ import React from 'react'; import Modal from 'react-bootstrap/Modal'; import cx from 'classnames'; -export default function AlertDialog({ title, size = 'sm', show, closeModal, customClassName, children }) { +export default function AlertDialog({ + title, + size = 'sm', + show, + closeModal, + customClassName, + children, + checkForBackground = false, +}) { const darkMode = localStorage.getItem('darkMode') === 'true'; + //checkForBackground :: remove this once all ui is revamped used only so that editor styles is unaltered return ( closeModal(false)} - contentClassName={cx('animation-fade home-modal-component', customClassName, { dark: darkMode })} + contentClassName={cx( + `animation-fade ${!checkForBackground ? 'home-modal-component' : 'home-modal-component-editor'} ${ + darkMode && 'dark-theme' + }`, + customClassName, + { + dark: checkForBackground && darkMode, + } + )} show={show} size={size} backdrop={'static'} @@ -18,7 +35,7 @@ export default function AlertDialog({ title, size = 'sm', show, closeModal, cust centered data-cy={'modal-component'} style={{ zIndex: 9992 }} - backdropClassName="home-modal-backdrop" + // backdropClassName="home-modal-backdrop" > {title && ( diff --git a/frontend/src/_ui/AppButton/AppButton.jsx b/frontend/src/_ui/AppButton/AppButton.jsx new file mode 100644 index 0000000000..f97938397d --- /dev/null +++ b/frontend/src/_ui/AppButton/AppButton.jsx @@ -0,0 +1,78 @@ +import React from 'react'; +import './AppButton.scss'; +import SolidIcon from '../Icon/solidIcons/index'; +import { Spinner } from 'react-bootstrap'; + +export const ButtonBase = function ButtonBase(props) { + const mapBaseSize = { + lg: 'tj-large-btn', + md: 'tj-medium-btn', + sm: 'tj-small-btn', + xs: 'tj-extra-small-btn', + }; + + const { + className, + size = 'lg', // specify size otherwise large button dimesnions will be applied with min width + as = 'button', // render it as a button or an anchor. + children, + disabled, + leftIcon, + rightIcon, + backgroundColor, + type, + isLoading, + fill, + iconCustomClass, + iconWidth, + ...restProps + } = props; + + const isAnchor = (!!restProps.href || as === 'a') && !disabled; + const Element = as ? as : isAnchor ? 'a' : 'button'; + + return ( + + {!isLoading && leftIcon && ( + + {} + + )} + {isLoading ? ( +
          + +
          + ) : ( + children + )} + {!isLoading && rightIcon && ( + + {} + + )} +
          + ); +}; + +export const ButtonSolid = function ButtonSolid(props) { + const mapVariant = { + primary: 'tj-primary-btn', + ghostBlue: 'tj-ghost-blue-btn', + ghostBlack: 'tj-ghost-black-btn', + secondary: 'tj-secondary-btn', + tertiary: 'tj-tertiary-btn', + dangerPrimary: 'tj-primary-danger-btn', + dangerSecondary: 'tj-secondary-danger-btn', + dangerTertiary: 'tj-tertiary-danger-btn', + dangerGhost: 'tj-ghost-danger-btn', + }; + + const { variant = 'primary', className, ...restProps } = props; + return ; +}; diff --git a/frontend/src/_ui/AppButton/AppButton.scss b/frontend/src/_ui/AppButton/AppButton.scss new file mode 100644 index 0000000000..5903d7fc00 --- /dev/null +++ b/frontend/src/_ui/AppButton/AppButton.scss @@ -0,0 +1,344 @@ +@import "../../_styles/designtheme.scss"; + +.tj-base-btn { + box-sizing: border-box; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 10px 20px; + gap: 8px; + border-radius: 6px; + font-weight: 600; + text-decoration: none; + cursor: pointer; + line-height: 20px; + text-decoration: none !important; + + .tj-btn-left-icon, + .tj-btn-right-icon { + display: flex; + align-items: center; + } + + &:disabled { + background: var(--slate3) !important; + color: var(--slate8) !important; + } +} + +.tj-large-btn { + height: 40px; + border-radius: 6px; + padding: 10px 20px 10px 20px; + font-size: 14px; +} + +.tj-medium-btn { + height: 32px; + border-radius: 6px; + padding: 6px 16px 6px 16px; + font-size: 14px; +} + +.tj-small-btn { + height: 28px; + border-radius: 6px; + padding: 4px 16px 4px 16px; + font-size: 12px; +} + +.tj-extra-small-btn { + height: 20px; + border-radius: 4px; + padding: 0px 8px 0px 8px; + font-size: 12px; +} + +/* Order */ +.tj-reverse-btn { + flex-direction: row-reverse; +} + +/* Variants */ +.tj-primary-btn { + background: #3E63DD; + border: none; + color: #FDFDFE; + + &:hover { + background: #3A5CCC; + color: #FDFDFE; + + } + + &:focus-visible { + box-shadow: 0px 0px 0px 4px var(--indigo6); + background: #3A5CCC; + outline: none; + color: #FDFDFE; + + } + + + &:active { + background: #3451B2; + box-shadow: none; + color: #F0F4FF; + } + +} + +.tj-secondary-btn { + background: var(--indigo3); + border: none; + color: var(--indigo11); + + &:hover { + background: var(--indigo4); + color: var(--indigo10); + } + + &:active { + background: var(--indigo5); + color: var(--indigo8); + box-shadow: none; + } + + &:focus-visible { + background: #E6EDFE; + box-shadow: 0px 0px 0px 4px var(--indigo6); + color: var(--indigo10); + outline: none; + } +} + +.tj-tertiary-btn { + background: var(--base); + border: 1px solid var(--slate7); + color: var(--slate12); + + &:hover { + background: var(--slate8); + color: var(--slate11); + border: 1px solid var(--slate8); + background: var(--base); + } + + &:active { + background: var(--base); + box-shadow: none; + border: 1px solid var(--slate12); + color: var(--slate12); + } + + &:focus-visible { + background: var(--base); + color: var(--slate11); + outline: 1px solid var(--slate8); + box-shadow: 0px 0px 0px 4px var(--slate6); + outline: none; + } +} + + +.tj-ghost-blue-btn { + color: var(--indigo9); + border: none; + background: transparent; + + &:hover { + color: var(--indigo10); + } + + &:active { + color: var(--indigo8); + } + + &:focus-visible { + color: var(--indigo10); + background: var(--base); + border: none; + box-shadow: 0px 0px 0px 4px var(--indigo6); + outline: none; + } +} + +.tj-ghost-black-btn { + color: var(--slate12); + border: none; + background: transparent; + + &:hover { + color: var(--slate11); + } + + &:active { + color: var(--slate12); + } + + &:focus-visible { + color: var(--slate11); + background: var(--base); + border: none; + box-shadow: 0px 0px 0px 4px var(--slate6); + outline: none; + } +} + +/* // Danger-variants--- */ + +.tj-primary-danger-btn { + background: #E54D2E; + border: none; + color: #FFFCFC; + + &:hover { + background: var(--tomato10); + } + + &:active { + background: var(--tomato11); + color: #F0F4FF + } + + &:focus-visible { + background: var(--tomato10); + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + outline: none; + } + +} + +.tj-secondary-danger-btn { + background: var(--tomato3); + border: none; + color: var(--tomato9); + + &:hover { + background: var(--tomato4); + color: var(--tomato10); + } + + &:active { + background: var(--tomato5); + color: var(--tomato8); + } + + &:focus-visible { + background: var(--tomato4); + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + color: var(--tomato8); + outline: none; + } +} + +.tj-tertiary-danger-btn { + background: var(--base); + border: 1px solid var(--tomato7); + color: var(--tomato9); + + &:hover { + background: var(--base); + color: var(--tomato10); + border: 1px solid var(--tomato5); + } + + &:active { + background: var(--base); + color: var(--tomato11); + border: 1px solid var(--tomato11); + } + + &:focus-visible { + background: var(--base); + outline: 1px solid var(--tomato5); + box-shadow: 0px 0px 0px 4px var(--tomato6); + color: var(--tomato10); + outline: none; + } + +} + + +.tj-ghost-danger-btn { + color: var(--tomato9); + background: transparent; + border: none; + + &:hover { + color: var(--tomato10); + } + + &:active { + color: var(--tomato8); + } + + &:focus-visible { + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + outline: none; + } +} + +.tj-disabled-btn { + background: var(--slate3) !important; + color: var(--slate8) !important; + border: none !important; +} + +.dark-theme { + tj-primary-btn { + background: #3E63DD; + border: none; + color: #FDFDFE; + + &:hover { + background: var(--indigo10); + border: 1px solid #FFFFFF + } + + &:focus-visible { + box-shadow: 0px 0px 0px 4px var(--indigo6); + border: 1px solid #FFFFFF; + outline: none; + } + + + &:active { + background: #849DFF; + box-shadow: none; + } + + } + + + /* // Danger-variants--- */ + + .tj-primary-danger-btn { + background: var(--tomato9); + border: none; + color: #FFFCFC; + + &:hover { + background: var(--tomato10); + color: #FFFCFC; + } + + &:active { + background: var(--tomato11); + color: #F0F4FF; + } + + &:focus-visible { + background: var(--tomato10); + border: none; + box-shadow: 0px 0px 0px 4px var(--tomato6); + outline: none; + color: #FFFCFC; + } + + } +} \ No newline at end of file diff --git a/frontend/src/_ui/AppInput/AppInput.jsx b/frontend/src/_ui/AppInput/AppInput.jsx new file mode 100644 index 0000000000..0f0712a77d --- /dev/null +++ b/frontend/src/_ui/AppInput/AppInput.jsx @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import './AppInput.scss'; + +const InputField = ({ + value, + label, + placeholder = '', + errorMessage, + type = 'input', + onChange, + disabled, + className = '', + currentState = 'none', +}) => { + const [data, setData] = useState(value); + const mapHelpers = { + success: 'tj-input-success', + warning: 'tj-input-warning', + error: 'tj-input-error', + helper: 'tj-input-helper', + }; + + const handleChange = (event) => { + const { value } = event.target; + onChange(value); + setData(data); + }; + + return ( +
          + {label && } + + {type === 'textarea' ? ( +