diff --git a/.github/workflows/docs-pr-app.yml b/.github/workflows/docs-pr-app.yml new file mode 100644 index 0000000000..69ef9a9306 --- /dev/null +++ b/.github/workflows/docs-pr-app.yml @@ -0,0 +1,222 @@ +name: Render PR deploy +on: + pull_request_target: + types: [labeled, unlabeled, closed] +env: + PR_NUMBER: ${{ github.event.number }} + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + +permissions: + pull-requests: write + issues: write + +jobs: + create-review-docs-app: + if: ${{ github.event.action == 'labeled' && github.event.label.name == 'create-review-docs-app' }} + runs-on: ubuntu-latest + + steps: + - name: Create deployment + id: create-deployment + run: | + export RESPONSE=$(curl --request POST \ + --url https://api.render.com/v1/services \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --header 'Authorization: Bearer ${{ secrets.RENDER_API_KEY }}' \ + --data ' + { + "type": "static_site", + "autoDeploy": "yes", + "branch": "${{ env.BRANCH_NAME }}", + "name": "ToolJet PR #${{ env.PR_NUMBER }}", + "ownerId": "tea-caeo4bj19n072h3dddc0", + "repo": "${{ github.event.pull_request.head.repo.git_url }}", + "rootDir": "docs", + "envVars": [ + { + "key": "NODE_ENV", + "value": "production" + }, + { + "key": "NODE_VERSION", + "value": "16.18.1" + }, + { + "key": "NPM_VERSION", + "value": "8.19.2" + }, + { + "key": "GA_MID", + "value": "dummy" + } + ], + "serviceDetails": { + "pullRequestPreviewsEnabled": "no", + "buildCommand": "npm i && npm run build", + "publishPath": "build/", + "url": "https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com" + } + }') + + echo "response: $RESPONSE" + export SERVICE_ID=$(echo $RESPONSE | jq -r '.service.id') + echo "SERVICE_ID=$SERVICE_ID" >> $GITHUB_ENV + + - name: Comment deployment URL + uses: actions/github-script@v5 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'Deployment: https://tooljet-pr-${{ env.PR_NUMBER }}.onrender.com \n Dashboard: https://dashboard.render.com/static/${{ env.SERVICE_ID }}' + }) + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'create-review-docs-app' + }) + } catch (e) { + console.log(e) + } + + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['active-review-docs-app'] + }) + + destroy-review-docs-app: + if: ${{ (github.event.action == 'labeled' && github.event.label.name == 'destroy-review-docs-app') || github.event.action == 'closed' }} + runs-on: ubuntu-latest + + steps: + - name: Delete service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request DELETE \ + --url https://api.render.com/v1/services/$SERVICE_ID \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'destroy-review-docs-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'suspend-review-docs-app' + }) + } catch (e) { + console.log(e) + } + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'active-review-docs-app' + }) + } catch (e) { + console.log(e) + } + + suspend-review-docs-app: + if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-review-docs-app' }} + runs-on: ubuntu-latest + + steps: + - name: Suspend service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request POST \ + --url https://api.render.com/v1/services/$SERVICE_ID/suspend \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'active-review-docs-app' + }) + } catch (e) { + console.log(e) + } + + resume-review-docs-app: + if: ${{ github.event.action == 'unlabeled' && github.event.label.name == 'suspend-review-docs-app' }} + runs-on: ubuntu-latest + + steps: + - name: Resume service + run: | + export SERVICE_ID=$(curl --request GET \ + --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ + jq -r '.[0].service.id') + + curl --request POST \ + --url https://api.render.com/v1/services/$SERVICE_ID/resume \ + --header 'accept: application/json' \ + --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' + + - uses: actions/github-script@v6 + with: + script: | + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['active-review-docs-app'] + }) + + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'suspend-review-docs-app' + }) + } catch (e) { + console.log(e) + } diff --git a/.github/workflows/stale-docs-pr-render-deploys.yml b/.github/workflows/stale-docs-pr-render-deploys.yml new file mode 100644 index 0000000000..a8cb4f2a7b --- /dev/null +++ b/.github/workflows/stale-docs-pr-render-deploys.yml @@ -0,0 +1,45 @@ +name: Label for stale Docs PR render deploys +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +permissions: + issues: write + +jobs: + label-stale-deploys: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: akshaysasidrn/stale-label-fetch@v1.1 + id: stale-label + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + stale-label: "active-review-docs-app" + stale-time: "86400" + type: "pull_request" + - name: Get stale numbers + run: echo "Matched PR numbers - ${{ steps.stale-label.outputs.stale-numbers }}" + - name: Add suspend label + uses: actions/github-script@v6 + env: + STALE_NUMBERS: ${{ steps.stale-label.outputs.stale-numbers }} + with: + github-token: ${{ secrets.TJ_BOT_PAT }} + script: | + if (!process.env.STALE_NUMBERS) return + + const prNumbers = process.env.STALE_NUMBERS.split(",") + + console.log(`Adding suspend labels for: ${prNumbers}`) + + for (const prNumber of prNumbers) { + github.rest.issues.addLabels({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['suspend-review-docs-app'] + }) + } diff --git a/.version b/.version index 83ecbf1d7a..db65e2167e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.20.2 +2.21.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 786388589d..d5e9f4f00f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,10 +17,9 @@ We use GitHub to host code, to track issues and feature requests, as well as acc ## First-time contributors We've tagged some issues to make it easy to get started :smile: -[Good first issues](https://github.com/ToolJet/ToolJet/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) -Looking for ReactJS issues? Check out our [Frontend issues](https://github.com/ToolJet/ToolJet/issues?q=is%3Aissue+is%3Aopen+label%3Afrontend) +[Good first issues](https://github.com/ToolJet/ToolJet/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) -Add a comment on the issue and wait for the issue to be assigned before you start working on it. This helps to avoid multiple people working on similar issues. +If you're interested in working on an issue, make sure it has either a `good-first-issue` or `up-for-grabs` label added. Add a comment on the issue and wait for the issue to be assigned before you start working on it. This helps to avoid multiple people working on similar issues. ## We Use [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow), So All Code Changes Happen Through Pull Requests Pull requests are the best way to propose changes to the codebase (we use [Git-Flow](https://nvie.com/posts/a-successful-git-branching-model/)). We actively welcome your pull requests: diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index 53ca1b7375..016af0bf06 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -56,6 +56,8 @@ Cypress.Commands.add("createApp", (appName) => { cy.get("body").then(($title) => { cy.get(getAppButtonSelector($title)).click(); + cy.clearAndType('[data-cy="app-name-input"]', appName); + cy.get('[data-cy="+ Create app"]').click(); }); cy.waitForAppLoad(); cy.skipEditorPopover(); diff --git a/cypress-tests/cypress/constants/texts/dashboard.js b/cypress-tests/cypress/constants/texts/dashboard.js index 47131ff553..3e8f1ab913 100644 --- a/cypress-tests/cypress/constants/texts/dashboard.js +++ b/cypress-tests/cypress/constants/texts/dashboard.js @@ -37,7 +37,7 @@ export const dashboardText = { }, seeAllAppsTemplateButton: "See all templates", addToFolderTitle: "Add to folder", - appClonedToast: "App cloned successfully.", + appClonedToast: "App cloned successfully!", darkModeText: "Dark Mode", lightModeText: "Light Mode", dashboardAppsHeaderLabel: " All apps", 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 a890da5760..b3ade9630e 100644 --- a/cypress-tests/cypress/e2e/editor/app-version/version.cy.js +++ b/cypress-tests/cypress/e2e/editor/app-version/version.cy.js @@ -42,9 +42,8 @@ describe("App Version Functionality", () => { }); it("Verify the elements of the version module", () => { - cy.createApp(); + cy.createApp(data.appName); cy.get(appVersionSelectors.appVersionLabel).should("be.visible"); - cy.renameApp(data.appName); cy.get(commonSelectors.appNameInput).verifyVisibleElement( "have.value", data.appName diff --git a/cypress-tests/cypress/e2e/workspace/dashboard.cy.js b/cypress-tests/cypress/e2e/workspace/dashboard.cy.js index 3a9274b1a7..e7d5c84ffb 100644 --- a/cypress-tests/cypress/e2e/workspace/dashboard.cy.js +++ b/cypress-tests/cypress/e2e/workspace/dashboard.cy.js @@ -168,12 +168,10 @@ describe("dashboard", () => { it("Should verify app card elements and app card operations", () => { cy.apiLogin(); - cy.apiCreateApp(); + cy.apiCreateApp(data.appName); cy.openApp(); - cy.renameApp(data.appName); cy.dragAndDropWidget("Table", 250, 250); - cy.get(commonSelectors.editorPageLogo).click(); cy.wait(500); @@ -192,7 +190,6 @@ describe("dashboard", () => { expect($el.contents().last().text().trim()).to.eq("The Developer"); }); }); - cy.reloadAppForTheElement(data.appName); viewAppCardOptions(data.appName); cy.get( @@ -213,7 +210,6 @@ describe("dashboard", () => { modifyAndVerifyAppCardIcon(data.appName); createFolder(data.folderName); - cy.reloadAppForTheElement(data.appName); viewAppCardOptions(data.appName); cy.get( @@ -246,7 +242,7 @@ describe("dashboard", () => { cy.get(commonSelectors.appCard(data.appName)) .contains(data.appName) .should("be.visible"); - cy.reloadAppForTheElement(data.appName); + viewAppCardOptions(data.appName); cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption)) @@ -256,7 +252,6 @@ describe("dashboard", () => { cancelModal(commonText.cancelButton); - cy.reloadAppForTheElement(data.appName); viewAppCardOptions(data.appName); cy.get( commonSelectors.appCardOptions(commonText.removeFromFolderOption) @@ -276,17 +271,13 @@ describe("dashboard", () => { deleteFolder(data.folderName); cy.get(commonSelectors.allApplicationsLink).click(); - cy.reloadAppForTheElement(data.appName); viewAppCardOptions(data.appName); cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - dashboardText.appClonedToast - ); - // cy.waitForAppLoad(); - cy.wait(2000); - cy.clearAndType(commonSelectors.appNameInput, data.cloneAppName); + cy.get('[data-cy="Clone app"]').click(); + cy.get('.go3958317564').should('be.visible').and('have.text', dashboardText.appClonedToast) + cy.wait(3000); + cy.renameApp(data.cloneAppName); cy.dragAndDropWidget("button", 25, 25); cy.get(commonSelectors.editorPageLogo).click(); cy.wait("@appLibrary"); @@ -341,12 +332,11 @@ describe("dashboard", () => { it("Should verify the app CRUD operation", () => { data.appName = `${fake.companyName}-App`; cy.appUILogin(); - cy.createApp(); - cy.renameApp(data.appName); + cy.createApp(data.appName); cy.dragAndDropWidget("Button", 450, 450); cy.get(commonSelectors.editorPageLogo).click(); - cy.reloadAppForTheElement(data.appName); + cy.get(commonSelectors.appCard(data.appName)).should( "contain.text", data.appName @@ -368,8 +358,7 @@ describe("dashboard", () => { it("Should verify the folder CRUD operation", () => { data.appName = `${fake.companyName}-App`; cy.appUILogin(); - cy.createApp(); - cy.renameApp(data.appName); + cy.createApp(data.appName); cy.dragAndDropWidget("Button", 100, 100); cy.get(commonSelectors.editorPageLogo).click(); @@ -431,7 +420,7 @@ describe("dashboard", () => { .should("be.visible") .and("have.text", "Edit folder"); - cy.get(commonSelectors.folderNameInput).should("be.visible") + cy.get(commonSelectors.folderNameInput).should("be.visible"); // verifyModal( // commonText.updateFolderTitle, diff --git a/cypress-tests/cypress/e2e/workspace/shareApp.cy.js b/cypress-tests/cypress/e2e/workspace/shareApp.cy.js index ff6c9452ed..b9391d303d 100644 --- a/cypress-tests/cypress/e2e/workspace/shareApp.cy.js +++ b/cypress-tests/cypress/e2e/workspace/shareApp.cy.js @@ -13,119 +13,121 @@ describe("App share functionality", () => { data.email = fake.email.toLowerCase(); const slug = data.appName.toLowerCase().replace(/\s+/g, "-"); const firstUserEmail = data.email + const envVar = Cypress.env("environment"); beforeEach(() => { cy.appUILogin(); }); - it("Verify private and public app share funtionality", () => { - cy.apiLogin(); - cy.apiCreateApp(); - cy.openApp(); - cy.renameApp(data.appName); - cy.dragAndDropWidget("Table", 250, 250); + if (envVar === "Community") { + it("Verify private and public app share funtionality", () => { + cy.apiLogin(); + cy.apiCreateApp(data.appName); + cy.openApp(); + cy.dragAndDropWidget("Table", 250, 250); - cy.get(commonWidgetSelector.shareAppButton).click(); + cy.get(commonWidgetSelector.shareAppButton).click(); - for (const elements in commonWidgetSelector.shareModalElements) { - cy.get( - commonWidgetSelector.shareModalElements[elements] - ).verifyVisibleElement( + for (const elements in commonWidgetSelector.shareModalElements) { + cy.get( + commonWidgetSelector.shareModalElements[elements] + ).verifyVisibleElement( + "have.text", + commonText.shareModalElements[elements] + ); + } + + cy.get(commonWidgetSelector.makePublicAppToggle).should("be.visible"); + cy.get(commonWidgetSelector.appLink).should("be.visible"); + cy.get(commonWidgetSelector.appNameSlugInput).should("be.visible"); + // cy.get(commonWidgetSelector.iframeLink).should("be.visible"); + cy.get(commonWidgetSelector.modalCloseButton).should("be.visible"); + + cy.clearAndType(commonWidgetSelector.appNameSlugInput, `${slug}`); + cy.get(commonWidgetSelector.modalCloseButton).click(); + cy.forceClickOnCanvas() + cy.dragAndDropWidget("Button", 50, 50); + cy.get(commonSelectors.editorPageLogo).click(); + + logout(); + cy.visit(`/applications/${slug}`); + + cy.get(commonSelectors.loginButton).should("be.visible"); + + cy.clearAndType(commonSelectors.workEmailInputField, "dev@tooljet.io"); + cy.clearAndType(commonSelectors.passwordInputField, "password"); + cy.get(commonSelectors.loginButton).click(); + + cy.wait(500); + cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); + cy.get(commonSelectors.viewerPageLogo).click(); + + navigateToAppEditor(data.appName); + cy.get(commonWidgetSelector.shareAppButton).click(); + cy.get(commonWidgetSelector.makePublicAppToggle).check(); + cy.get(commonWidgetSelector.modalCloseButton).click(); + cy.get(commonSelectors.editorPageLogo).click(); + + logout(); + cy.visit(`/applications/${slug}`); + cy.wait(500); + cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); + }); + + it("Verify app private and public app visibility for the same workspace user", () => { + addNewUserMW(data.firstName, data.email); + + logout(); + cy.visit(`/applications/${slug}`); + cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); + + cy.appUILogin(); + navigateToAppEditor(data.appName); + cy.skipEditorPopover() + cy.get(commonWidgetSelector.shareAppButton).click(); + cy.get(commonWidgetSelector.makePublicAppToggle).uncheck(); + cy.get(commonWidgetSelector.modalCloseButton).click(); + cy.get(commonSelectors.editorPageLogo).click(); + + logout(); + cy.visit(`/applications/${slug}`); + + cy.login(data.email, "password"); + cy.get(commonSelectors.allApplicationLink).verifyVisibleElement( "have.text", - commonText.shareModalElements[elements] + commonText.allApplicationLink ); - } + }); - cy.get(commonWidgetSelector.makePublicAppToggle).should("be.visible"); - cy.get(commonWidgetSelector.appLink).should("be.visible"); - cy.get(commonWidgetSelector.appNameSlugInput).should("be.visible"); - // cy.get(commonWidgetSelector.iframeLink).should("be.visible"); - cy.get(commonWidgetSelector.modalCloseButton).should("be.visible"); + it("Verify app private and public app visibility for the same instance user", () => { + data.firstName = fake.firstName; + data.email = fake.email.toLowerCase(); - cy.clearAndType(commonWidgetSelector.appNameSlugInput, `${slug}`); - cy.get(commonWidgetSelector.modalCloseButton).click(); - cy.forceClickOnCanvas() - cy.dragAndDropWidget("Button", 50, 50); - cy.get(commonSelectors.editorPageLogo).click(); + logout(); + userSignUp(data.firstName, data.email, "Test"); + cy.visit(`/applications/${slug}`); + cy.wait(1000); - logout(); - cy.visit(`/applications/${slug}`); + cy.clearAndType(commonSelectors.workEmailInputField, data.email); + cy.clearAndType(commonSelectors.passwordInputField, "password"); + cy.get(commonSelectors.signInButton).click(); + cy.wait(1000); - cy.get(commonSelectors.loginButton).should("be.visible"); + cy.visit("/"); + cy.wait(2000); + logout(); + cy.appUILogin(); - cy.clearAndType(commonSelectors.workEmailInputField, "dev@tooljet.io"); - cy.clearAndType(commonSelectors.passwordInputField, "password"); - cy.get(commonSelectors.loginButton).click(); + navigateToAppEditor(data.appName); + cy.skipEditorPopover(); + cy.get(commonWidgetSelector.shareAppButton).click(); + cy.get(commonWidgetSelector.makePublicAppToggle).check(); + cy.get(commonWidgetSelector.modalCloseButton).click(); + cy.get(commonSelectors.editorPageLogo).click(); - cy.wait(500); - cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); - cy.get(commonSelectors.viewerPageLogo).click(); - - navigateToAppEditor(data.appName); - cy.get(commonWidgetSelector.shareAppButton).click(); - cy.get(commonWidgetSelector.makePublicAppToggle).check(); - cy.get(commonWidgetSelector.modalCloseButton).click(); - cy.get(commonSelectors.editorPageLogo).click(); - - logout(); - cy.visit(`/applications/${slug}`); - cy.wait(500); - cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); - }); - - it("Verify app private and public app visibility for the same workspace user", () => { - addNewUserMW(data.firstName, data.email); - - logout(); - cy.visit(`/applications/${slug}`); - cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); - - cy.appUILogin(); - navigateToAppEditor(data.appName); - cy.skipEditorPopover() - cy.get(commonWidgetSelector.shareAppButton).click(); - cy.get(commonWidgetSelector.makePublicAppToggle).uncheck(); - cy.get(commonWidgetSelector.modalCloseButton).click(); - cy.get(commonSelectors.editorPageLogo).click(); - - logout(); - cy.visit(`/applications/${slug}`); - - cy.login(data.email, "password"); - cy.get(commonSelectors.allApplicationLink).verifyVisibleElement( - "have.text", - commonText.allApplicationLink - ); - }); - - it("Verify app private and public app visibility for the same instance user", () => { - data.firstName = fake.firstName; - data.email = fake.email.toLowerCase(); - - logout(); - userSignUp(data.firstName, data.email, "Test"); - cy.visit(`/applications/${slug}`); - cy.wait(1000); - - cy.clearAndType(commonSelectors.workEmailInputField, data.email); - cy.clearAndType(commonSelectors.passwordInputField, "password"); - cy.get(commonSelectors.signInButton).click(); - cy.wait(1000); - - cy.visit("/"); - cy.wait(2000); - logout(); - cy.appUILogin(); - - navigateToAppEditor(data.appName); - cy.skipEditorPopover(); - cy.get(commonWidgetSelector.shareAppButton).click(); - cy.get(commonWidgetSelector.makePublicAppToggle).check(); - cy.get(commonWidgetSelector.modalCloseButton).click(); - cy.get(commonSelectors.editorPageLogo).click(); - - logout(); - cy.visit(`/applications/${slug}`); - cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); - cy.get(commonSelectors.viewerPageLogo).click(); - }); + logout(); + cy.visit(`/applications/${slug}`); + cy.get('[data-cy="draggable-widget-table1"]').should("be.visible"); + cy.get(commonSelectors.viewerPageLogo).click(); + }); + } }); \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/workspace/userPermissions.cy.js b/cypress-tests/cypress/e2e/workspace/userPermissions.cy.js index b6ebcf5416..6911368965 100644 --- a/cypress-tests/cypress/e2e/workspace/userPermissions.cy.js +++ b/cypress-tests/cypress/e2e/workspace/userPermissions.cy.js @@ -23,11 +23,9 @@ describe("User permissions", () => { permissions.reset(); cy.get(commonSelectors.homePageLogo).click(); cy.wait("@homePage"); - cy.createApp(); - cy.renameApp(data.appName); + cy.createApp(data.appName); cy.dragAndDropWidget("Table", 250, 250); cy.get(commonSelectors.editorPageLogo).click(); - cy.reloadAppForTheElement(data.appName); permissions.addNewUserMW(data.firstName, data.email); common.logout(); }); @@ -41,11 +39,7 @@ describe("User permissions", () => { cy.login(data.email, usersText.password); cy.get("body").then(($title) => { if ($title.text().includes(dashboardText.emptyPageDescription)) { - cy.get(commonSelectors.dashboardAppCreateButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - usersText.createAppPermissionToast - ); + cy.get(commonSelectors.dashboardAppCreateButton).should('be.disabled'); } else { cy.contains(dashboardText.createAppButton).should("not.exist"); } @@ -120,7 +114,21 @@ describe("User permissions", () => { }); it("Should verify the Create and Delete app permission", () => { + data.appName = `${fake.companyName}-App`; + cy.createApp(data.appName); + cy.get(commonSelectors.editorPageLogo).click(); + cy.wait(1000); common.navigateToManageGroups(); + cy.get(groupsSelector.appSearchBox).click(); + cy.get(groupsSelector.searchBoxOptions).contains(data.appName).click(); + cy.get(groupsSelector.selectAddButton).click(); + cy.get("table").contains("td", data.appName); + cy.contains("td", data.appName) + .parent() + .within(() => { + cy.get("td input").first().should("be.checked"); + }); + cy.wait(500) cy.get(groupsSelector.permissionsLink).click(); cy.get(groupsSelector.appsCreateCheck).check(); cy.get(groupsSelector.permissionsLink).click(); @@ -141,11 +149,10 @@ describe("User permissions", () => { common.viewAppCardOptions(data.appName); cy.contains("Delete app").should("not.exist"); - cy.createApp(); - cy.renameApp(data.email); + cy.createApp(data.email); + cy.dragAndDropWidget("Table", 50, 50); cy.get(commonSelectors.editorPageLogo).click(); - cy.reloadAppForTheElement(data.email); common.viewAppCardOptions(data.email); cy.contains("Delete app").should("exist"); cy.get(commonSelectors.appCardOptions(commonText.deleteAppOption)).click(); diff --git a/cypress-tests/cypress/e2e/workspace/workspaceConstants.cy.js b/cypress-tests/cypress/e2e/workspace/workspaceConstants.cy.js index e5c4f3b26b..0d8258136b 100644 --- a/cypress-tests/cypress/e2e/workspace/workspaceConstants.cy.js +++ b/cypress-tests/cypress/e2e/workspace/workspaceConstants.cy.js @@ -279,8 +279,7 @@ describe("Workspace constants", () => { cy.get(commonSelectors.homePageLogo).click(); cy.wait("@homePage"); - cy.createApp(); - cy.renameApp(data.appName); + cy.createApp(data.appName); selectQueryFromLandingPage("runjs", "JavaScript"); addInputOnQueryField("runjs", `return constants.${data.constantsName}`); diff --git a/docs/docs/data-sources/appwrite.md b/docs/docs/data-sources/appwrite.md index dc11572625..c987afdd6b 100644 --- a/docs/docs/data-sources/appwrite.md +++ b/docs/docs/data-sources/appwrite.md @@ -1,11 +1,11 @@ --- id: appwrite -title: Appwrite Database +title: Appwrite --- -# Appwrite Database +# Appwrite -Now build applications on top of your Appwrite database. +ToolJet can connect to appwrite database to read/write data. ## Connection @@ -20,7 +20,7 @@ You'll find the Secret key and other credentials on your Appwrite's project sett You should also set the scope for access to a particular resource. Learn more about the **API keys and scopes** [here](https://appwrite.io/docs/keys). ::: -To connect Appwrite datasource to your ToolJet application, go to the data source manager on the left-sidebar and click on the `+` button. Select Appwrite from the list of available datasources, provide the credentials and click **Save**. It is recommended to check the connection by clicking on 'Test connection' button to verify if the service account can access Appwrite from the ToolJet server. +To establish a connection with the Appwrite data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard.
diff --git a/docs/docs/data-sources/bigquery.md b/docs/docs/data-sources/bigquery.md index e3e29b8d4c..2b6eb57145 100644 --- a/docs/docs/data-sources/bigquery.md +++ b/docs/docs/data-sources/bigquery.md @@ -9,15 +9,13 @@ ToolJet can connect to BigQuery databases to run BigQuery queries. ## Connection -Please refer [this](https://cloud.google.com/bigquery/docs/bigquery-web-ui) link to enable BigQuery API in Google Cloud Console. +To connect to BigQuery, you need to enable BigQuery API in your Google Cloud Console. You can follow the steps to enable BigQuery API from [this link](https://cloud.google.com/bigquery/docs/bigquery-web-ui). -Create **Service Account** and **key**, then get your configs downloaded as **JSON** +Next, you need to create a service account and generate a key for the same. You can follow the steps to create a service account from [this link](https://cloud.google.com/iam/docs/creating-managing-service-accounts). -To add a new BigQuery, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select BigQuery from the modal that pops up. +Now, copy and paste the data from the downloaded JSON file into the **Private key** field in the BigQuery data source form. -ToolJet requires the config json downloaded from your account to connect to BigQuery. Paste the json into the `Private key` field. - -**The json looks like**: +**The json file should look like this:** ```json { @@ -50,7 +48,6 @@ Click on `+` button of the query manager at the bottom panel of the editor and s BQ query -Click on the **run** button to run the query. NOTE: Query should be saved before running. :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) @@ -157,4 +154,4 @@ NOTE: visit -https://github.com/googleapis/nodejs-bigquery/blob/main/samples/cre ::: ### Delete Table -- To delete a table. \ No newline at end of file +- To delete a table. diff --git a/docs/docs/data-sources/cosmosdb.md b/docs/docs/data-sources/cosmosdb.md index a924932750..9ffe9b3e6d 100644 --- a/docs/docs/data-sources/cosmosdb.md +++ b/docs/docs/data-sources/cosmosdb.md @@ -2,14 +2,14 @@ id: cosmosdb title: CosmosDB --- + # Cosmosdb -ToolJet can connect to CosmosDB databases to read and write data. - +ToolJet can connect to CosmosDB databases to read and write data. ## Connection -To add a new **[Azure Cosmos DB](https://docs.microsoft.com/en-us/javascript/api/overview/azure/cosmos-readme?view=azure-node-latest#key-concepts)**, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select CosmosDB from the modal that pops up. +To establish a connection with the CosmosDB data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. ToolJet requires the following to connect to your Cosmos DB. @@ -28,8 +28,7 @@ You can find the endpoint and key in the **[Azure Portal](https://portal.azure.c
- -## Supported queries: +## Supported Queries: - [Listing databases](#listing-databases) - [Listing containers](#listing-containers) @@ -38,54 +37,54 @@ You can find the endpoint and key in the **[Azure Portal](https://portal.azure.c - [Deleting an item](#deleting-an-item) - [Querying documents](#querying-documents) +### Listing Databases - - - -### Listing databases This query lists all the databases in a Cosmos DB. -### Listing containers +### Listing Containers + This query lists all the containers of a database in a Cosmos DB. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | +| Fields | Description | +| -------- | ------------------ | +| database | id of the database | + +### Inserting Item(s) -### Inserting item(s) This query inserts one or more items in a container of a database in a Cosmos DB. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| items | items to be inserted. Example: `{{[{name: "one", val: 1}, {name:"two", val: 2}]}}` | +| Fields | Description | +| --------- | ---------------------------------------------------------------------------------- | +| database | id of the database | +| container | id of the container | +| items | items to be inserted. Example: `{{[{name: "one", val: 1}, {name:"two", val: 2}]}}` | + +### Retrieving An Item -### Retrieving an item To read a single item from a container of a database in a Cosmos DB, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| item | id of the item | +| Fields | Description | +| --------- | ------------------- | +| database | id of the database | +| container | id of the container | +| item | id of the item | +### Deleting An Item -### Deleting an item To delete an item from a container of a database in a Cosmos DB, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| item | id of the item | +| Fields | Description | +| --------- | ------------------- | +| database | id of the database | +| container | id of the container | +| item | id of the item | +### Querying Documents -### Querying documents To query documents from a container of a database in a Cosmos DB using SQL-like syntax, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| query | query to be executed. Example: `SELECT * FROM c WHERE c.age > 20 AND c.age <= 30` | +| Fields | Description | +| --------- | --------------------------------------------------------------------------------- | +| database | id of the database | +| container | id of the container | +| query | query to be executed. Example: `SELECT * FROM c WHERE c.age > 20 AND c.age <= 30` | diff --git a/docs/docs/data-sources/couchdb.md b/docs/docs/data-sources/couchdb.md index 385ed788ad..d531ac0042 100644 --- a/docs/docs/data-sources/couchdb.md +++ b/docs/docs/data-sources/couchdb.md @@ -8,27 +8,24 @@ title: CouchDB ToolJet can connect to CouchDB databases to read and write data. CocuhDB uses basic auth for authentication , username and password for the database is required to create an CouchDB data source on ToolJet. For more info visit [CouchDB docs](https://docs.couchdb.org/en/stable/). -Couch auth +## Supported Queries: - -## Supported queries: - -- [Listing records](#listing-records) -- [Retrieving a record](#retrieving-a-record) -- [Creating a record](#creating-a-record) -- [Updating a record](#updating-a-record) -- [Deleting a record](#deleting-a-record) +- [Listing Records](#listing-records) +- [Retrieving a Record](#retrieving-a-record) +- [Creating a Record](#creating-a-record) +- [Updating a Record](#updating-a-record) +- [Deleting a Record](#deleting-a-record) - [Find](#find) -- [Retrieving a view](#retrieving-a-view) +- [Retrieving a View](#retrieving-a-view) :::info NOTE: Record ID is same as document ID("_id") . ::: -### Listing records +### Listing Records This query lists all the records in a database. -#### Optional parameters: +#### Optional Parameters: - **Include docs** - **Descending order** @@ -83,9 +80,9 @@ Example response from CouchDb: } ``` -### Retrieving a record +### Retrieving a Record -#### Required parameters: +#### Required Parameters: - **Record ID** @@ -108,7 +105,7 @@ Example response from CouchDb: The returned JSON is the JSON of the document, including the document ID and revision number: -### Creating a record +### Creating a Record Couch create view @@ -122,10 +119,6 @@ The returned JSON is the JSON of the document, including the document ID and rev Click on the `run` button to run the query. -:::info -NOTE: Query must be saved before running. -::: - Example response from CouchDb: ```json @@ -137,12 +130,12 @@ Example response from CouchDb: ``` -### Updating a record +### Updating a Record You can get the revision id value, by sending a GET request to get the document details. You get the document as JSON in the response. For each update to the document, the revision field "_rev" gets changed. -#### Required parameters: +#### Required Rarameters: - **Revision ID** - **Record ID** @@ -150,7 +143,7 @@ You get the document as JSON in the response. For each update to the document, t Couch update view -#### Example body: +#### Example Body: ```json [{"name":"tooljet"}] @@ -172,9 +165,9 @@ Example response from CouchDb: } ``` -### Deleting a record +### Deleting a Record -#### Required parameters: +#### Required Parameters: - **Revision ID** - **Record ID** @@ -200,7 +193,7 @@ Example response from CouchDb: Find documents using a declarative JSON querying syntax. -#### Required parameters: +#### Required Parameters: - **Selector** :::info @@ -212,7 +205,7 @@ selector syntax: https://pouchdb.com/guides/mango-queries.html Couch find -#### Example body: +#### Example Body: ```json { @@ -239,11 +232,11 @@ Example response from CouchDb: Couch find response -### Retrieving a view +### Retrieving a View Views are the primary tool used for querying and reporting on CouchDB documents. -#### Required parameters: +#### Required Parameters: - **View url** Reference for view :https://docs.couchdb.org/en/3.2.0/ddocs/views/intro.html#what-is-a-view @@ -252,7 +245,7 @@ Reference for view :https://docs.couchdb.org/en/3.2.0/ddocs/views/intro.html#wha Couch get view -#### Optional parameters: +#### Optional Parameters: - **Start key** - **End key** diff --git a/docs/docs/data-sources/dynamodb.md b/docs/docs/data-sources/dynamodb.md index abaacaa48c..127a917898 100644 --- a/docs/docs/data-sources/dynamodb.md +++ b/docs/docs/data-sources/dynamodb.md @@ -8,7 +8,7 @@ DynamoDB is a managed non-relational database service provided by Amazon. ToolJe ## Connection -To establish a connection with the DynamoDB global datasource, you can either click on the `+Add new global datasource` button located on the query panel or navigate to the **[Global Datasources](/docs/data-sources/overview)** page through the ToolJet dashboard. +To establish a connection with the DynamoDB data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data sources](/docs/data-sources/overview)** page through the ToolJet dashboard.
diff --git a/docs/docs/data-sources/elasticsearch.md b/docs/docs/data-sources/elasticsearch.md index 3f06fc7b3d..6c996df16e 100644 --- a/docs/docs/data-sources/elasticsearch.md +++ b/docs/docs/data-sources/elasticsearch.md @@ -9,7 +9,7 @@ ToolJet can connect to your Elasticsearch cluster to read and write data. ## Connection Please make sure the host/IP of the Elasticsearch cluster is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please **whitelist our IP**. -To add a new Elasticsearch database, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select Elasticsearch from the modal that pops up. +To establish a connection with the ElasticSearch data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. ToolJet requires the following to connect to your Elasticsearch cluster: - **Host** @@ -24,7 +24,7 @@ ToolJet requires the following to connect to your Elasticsearch cluster:
-Elastic search datasource is also providing an option for connecting services with ssl certificates. +Elastic search data source is also providing an option for connecting services with ssl certificates. - You can either use CA / Client certificates option. Elastic ssl diff --git a/docs/docs/data-sources/firestore.md b/docs/docs/data-sources/firestore.md index c0375c29c6..4b86322ea5 100644 --- a/docs/docs/data-sources/firestore.md +++ b/docs/docs/data-sources/firestore.md @@ -4,12 +4,14 @@ title: Cloud Firestore --- # Cloud Firestore +ToolJet can connect to Cloud Firestore databases to read and write data. ## Connection -ToolJet connects to your Cloud Firestore using JSON key of your GCP service account. -To generate a new key, check out [Firestore's official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console). +ToolJet connects to your Cloud Firestore using JSON key of your GCP service account. Get your service account key as JSON from GCP console. For generating a new key, check out [Firestore's official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console). -Once the key is downloaded, click on `+` button of data sources panel at the left-bottom corner of the app editor. Select Firestore from the modal that pops up. Paste the key in the field for GCP key. Click on **Test connection** button to verify if the service account can access Firestore from ToolJet server. Click on **Save** button to save the datasource. +Once you have the key, open it in a text editor and copy the contents. Paste the contents in the **Private key** field of the Firestore data source modal. + +Click on **Test connection** button to verify if the key is valid. Click on **Save** button to save the data source. firestore add ds @@ -160,4 +162,4 @@ The Firestore query result is in the form of object so we’ll need to transform ```js return data = Array(data) -``` \ No newline at end of file +``` diff --git a/docs/docs/data-sources/gcs.md b/docs/docs/data-sources/gcs.md index 6483a43408..03cad29535 100644 --- a/docs/docs/data-sources/gcs.md +++ b/docs/docs/data-sources/gcs.md @@ -9,19 +9,18 @@ ToolJet can connect to GCS buckets and perform various operation on them. ## Supported operations --**Read file** --**Upload file** --**List buckets** --**List files in a bucket** --**Signed url for download** --**Signed url for upload** +- **Read file** +- **Upload file** +- **List buckets** +- **List files in a bucket** +- **Signed url for download** +- **Signed url for upload** ## Connection -To add a new GCS source, click on the **Add or edit datasource** icon on the left sidebar of the app editor and click on `Add datasource` button. Select GCS from the modal that pops up. +To establish a connection with the Google Cloud Storage data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. -ToolJet requires the **json private key** of a service account to be able to connect to GCS. -You can follow the [google documentation](https://cloud.google.com/docs/authentication/getting-started) to get started. +To connect to GCS, you need to provide the JSON Private Key of a service account that has access to the bucket. You can follow the [google documentation](https://cloud.google.com/docs/authentication/getting-started) to get started. gcs connection diff --git a/docs/docs/data-sources/mailgun.md b/docs/docs/data-sources/mailgun.md index 6b4c71ca78..09907b82d1 100644 --- a/docs/docs/data-sources/mailgun.md +++ b/docs/docs/data-sources/mailgun.md @@ -15,7 +15,7 @@ The Mailgun API Datasource supports for interaction with the mail endpoint of th ## Connection -To add a new Mailgun API datasource, click the **Datasource manager** icon on the left-sidebar of the app builder and click on the `Add datasource` button, then select Mailgun API from the modal that pops up. +To establish a connection with the MailGun data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. Enter your **Mailgun API key** in the "API key" field. @@ -58,7 +58,3 @@ For example: `admin@tooljet.io` **Send multiple individual emails to multiple recipients** - set Multiple recipients field to `{{true}}` and the `Send mail to` field will be split into multiple emails and send to each recipient. ::: - -:::note -NOTE: Query should be saved before running. -::: diff --git a/docs/docs/data-sources/minio.md b/docs/docs/data-sources/minio.md index 3f19ffc32d..0ac53fd17d 100644 --- a/docs/docs/data-sources/minio.md +++ b/docs/docs/data-sources/minio.md @@ -20,7 +20,7 @@ ToolJet can connect to minio and perform various operation on them. ## Connection -To add a new minio source, click on the **Add or edit datasource** icon on the left sidebar of the app editor and click on `Add datasource` button. Select Minio from the modal that pops up. +To establish a connection with the Minio data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your DynamoDB: @@ -44,7 +44,7 @@ Click on `+` button of the **query manager** at the bottom panel of the editor a miniIo query Click on the **run** button to run the query. -**NOTE**: Query should be saved before running. + :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) diff --git a/docs/docs/data-sources/mongodb.md b/docs/docs/data-sources/mongodb.md index 0ae21b519a..8a4dea7b16 100644 --- a/docs/docs/data-sources/mongodb.md +++ b/docs/docs/data-sources/mongodb.md @@ -11,7 +11,7 @@ ToolJet can connect to MongoDB to read and write data. Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. -To add a new MongoDB, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select MongoDB from the modal that pops up. +To establish a connection with the MongoDB data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your MongoDB. @@ -32,15 +32,14 @@ Click on `+` button of the query manager at the bottom panel of the editor and s ToolJet - Mongo query - - -Click on the 'run' button to run the query. NOTE: Query should be saved before running. +Click on the 'run' button to run the query. :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) ::: ### Supported operations + - [List Collections](#list-collections) - [Find One](#find-one) - [Find Many](#find-many) @@ -59,51 +58,89 @@ Query results can be transformed using transformations. Read our transformations - [Delete One](#delete-one) - [Delete Many](#delete-many) - [Bulk Operations](#bulk-operations) + #### List Collections + Returns list of collections + #### Fine One + Return a document which satisfy the given filter and options. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/findOne) + #### Fine Many + Return list of documents which satisfy the given filter and options. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/find/) + #### Total Count + Returns an estimation of the number of documents in the collection based on collection metadata. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#estimateddocumentcount) + #### Count + Returns the number of documents based on the filter. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#countdocuments) + #### Distinct + Retrieve a list of distinct values for a field based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/distinct/) + #### Insert One + Insert a document. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/insertOne/) + #### Insert Many + Insert list of documents. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/insertMany/) + #### Update One + Update a document based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/updateOne/) + #### Update Many + Update many documents based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/updateMany/) + #### Replace One + Replace a document based on filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/replaceOne/) + #### Find One and Update + If your application requires the document after updating, use this instead of `Update One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneandupdate) + #### Find One and Replace + If your application requires the document after updating, use this instead of `Replace One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneandreplace) + #### Find One and Delete + If your application requires the document after deleting, use this instead of `Delete One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneanddelete) + #### Aggregate + Aggregation operations are expressions you can use to produce reduced and summarized results. [Reference](https://docs.mongodb.com/drivers/node/v4.0/fundamentals/aggregation/) + #### Delete One + Delete a record based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/deleteOne/) + #### Delete Many + Delete many records based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/deleteMany/) + #### Bulk Operations + Perform bulk operations. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/bulkWrite/) ### Dynamic Quries + ```javascript { amount: { $lt: '{{ components.textinput1.value }}' }} -// Dates +// Dates // supported: Extended JSON syntax { createdAt: { $date: '{{ new Date('01/10/2020') }}'} } // not supported: MongoDB classic syntax { createdAt: new Date('01/10/2020') } ``` + Reference on [mongodb extended JSON](https://docs.mongodb.com/manual/reference/mongodb-extended-json/) supported data types diff --git a/docs/docs/data-sources/mssql.md b/docs/docs/data-sources/mssql.md index a8aa169ce5..0f9fa32b2f 100644 --- a/docs/docs/data-sources/mssql.md +++ b/docs/docs/data-sources/mssql.md @@ -12,7 +12,7 @@ ToolJet can connect to MS SQL Server & Azure SQL databases to read and write dat Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. -To add new MS SQL Server / Azure SQL database, click on the '+' button on data sources panel at the left-bottom corner of the app editor. Select `SQL Server` from the modal that pops up. +To establish a connection with the MS SQL Server data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your PostgreSQL database. @@ -32,7 +32,7 @@ Click on 'Test connection' button to verify if the credentials are correct and t ## Querying SQL Server / Azure SQL databases Click on '+' button of the query manager at the bottom panel of the editor and select the database added in the previous step as the data source. -Click on the 'run' button to run the query. NOTE: Query should be saved before running. +Click on the 'run' button to run the query. ToolJet - Redis connection diff --git a/docs/docs/data-sources/mysql.md b/docs/docs/data-sources/mysql.md index 63af848971..c349d83171 100644 --- a/docs/docs/data-sources/mysql.md +++ b/docs/docs/data-sources/mysql.md @@ -7,11 +7,11 @@ ToolJet can connect to MySQL databases to read and write data. ## Connection -To establish a connection with the MySQL datasource, you can either click on the `+Add New` button located on the query panel or navigate to the **[Global Datasources](/docs/data-sources/overview)** page through the ToolJet dashboard. +To establish a connection with the MySQL data source, you can either click on the `+Add New` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard.
-MySQL datasource +MySQL data source

@@ -20,31 +20,30 @@ To establish a connection with the MySQL datasource, you can either click on the Please make sure the **Host/IP** of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please **whitelist** our IP. ::: - **ToolJet requires the following to connect to your MySQL database:** -| Parameter | Description | -|:--- |:--- | -| Username | Username of the MySQL database | -| Password | Password of the MySQL database | -| Database name | Name of the MySQL database | +| Parameter | Description | +| :-------------- | :------------------------------------------------------------------------ | +| Username | Username of the MySQL database | +| Password | Password of the MySQL database | +| Database name | Name of the MySQL database | | Connection type | Connection type of the MySQL database: either **Hostname** or **Socket**. | If you are using **Hostname** as the connection type, you will need to provide the following information: -| Parameter | Description | -|:--- |:--- | -| Host/IP | Hostname or IP address of the MySQL database | -| Port | Port number of the MySQL database | -| SSL | Enable SSL connection to the MySQL database | +| Parameter | Description | +| :-------- | :------------------------------------------- | +| Host/IP | Hostname or IP address of the MySQL database | +| Port | Port number of the MySQL database | +| SSL | Enable SSL connection to the MySQL database | If you are using **Socket** as the connection type, you will need to provide the following information: -| Parameter | Description | -|:--- |:--- | +| Parameter | Description | +| :---------- | :---------------------- | | Socket path | Path of the socket file | -It is recommended to create a new MySQL database user so that you can control the access levels of ToolJet. +It is recommended to create a new MySQL database user so that you can control the access levels of ToolJet.
@@ -63,9 +62,10 @@ Once the MySQL data source is added, you can create queries to read and write da ### SQL mode -SQL mode can be used to query MySQL database using SQL queries. Select SQL mode from the dropdown and then enter the SQL query in the editor. +SQL mode can be used to query MySQL database using SQL queries. Select SQL mode from the dropdown and then enter the SQL query in the editor. **Example:** + ```sql SELECT * FROM users ``` @@ -80,7 +80,8 @@ SELECT * FROM users GUI mode can be used to query MySQL database without writing queries. Select GUI mode from the dropdown and then choose the operation **Bulk update using primary key**. Enter the **Table** name and **Primary key column** name. Now, in the editor enter the records in the form of an array of objects. Each object should contain the primary key column and the columns to be updated. -**Example:** +**Example:** + ```json {{ [ {id: 1, channel: 33}, {id:2, channel:24} ] }} ``` @@ -93,4 +94,4 @@ GUI mode can be used to query MySQL database without writing queries. Select GUI :::tip Query results can be transformed using transformations. Learn more about transformations [here](/docs/tutorial/transformations). -::: \ No newline at end of file +::: diff --git a/docs/docs/data-sources/notion.md b/docs/docs/data-sources/notion.md index 88000f3aa6..a224116642 100644 --- a/docs/docs/data-sources/notion.md +++ b/docs/docs/data-sources/notion.md @@ -2,11 +2,15 @@ id: notion title: Notion --- + # Notion ToolJet can connect to a Notion workspace to do operations on notion pages, databases and blocks. + ## Connection +To establish a connection with the Notion data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. + For integrating Notion with ToolJet we will need the API token. The API token can be generated from your Notion workspace settings. Read the official Notion docs for [Creating an internal integration with notion API](https://www.notion.so/help/create-integrations-with-the-notion-api).
@@ -18,37 +22,33 @@ For integrating Notion with ToolJet we will need the API token. The API token ca ## Querying Notion Notion API provides support for: + - **[Database](#database)** - **[Page](#page)** - **[Block](#blocks)** - **[User](#user)** - notion querying - :::tip Before querying Notion, you must share the database with your integration. Click the share button in your database view, find your integration name select it. - notion share - ::: ### Database On database resource you can perform the following operations: + - **[Retrieve a database](#1-retrieve-a-database)** - **[Query a database](#2-query-a-database)** - **[Create a database](#3-create-a-database)** - **[Update a database](#4-update-a-database)** - notion db - #### 1. Retrieve a database This operations retrieves a Database object using the ID specified. @@ -57,13 +57,12 @@ This operations retrieves a Database object using the ID specified. - **Database ID**: You'll find the Database ID in the url. Suppose this is the example url: `https://www.notion.so/workspace/XXX?v=YYY&p=ZZZ` then `XXX` is the database ID, `YYY` is the view ID and `ZZZ` is the page ID. - notion db retreieve - #### 2. Query a database This operation gets a list of **Pages** contained in the database, filtered and ordered according to the filter conditions and sort criteria provided in the query. + ##### Required parameters: - **Database ID** : You'll find the Database ID in the url. Suppose this is the example url: `https://www.notion.so/workspace/XXX?v=YYY&p=ZZZ` then `XXX` is the database ID, `YYY` is the view ID and `ZZZ` is the page ID. @@ -98,6 +97,7 @@ This operation creates a database as a subpage in the specified parent page, wit This operation updates an existing database as specified by the parameters. ##### Required parameters: + - **Database ID** ##### Optional parameters: @@ -112,24 +112,29 @@ This operation updates an existing database as specified by the parameters. ### Page On page resource you can perform the following operations: + - **[Retrieve a page](#1-retrieve-a-page)** - **[Create a page](#2-create-a-page)** - **[Update a page](#3-update-a-page)** - **[Retrieve a page property](#4-retrieve-a-page-property-item)** - **[Archive a page](#5-archive-delete-a-page)** - notion page - #### 1. Retrieve a page + This operation retrieves a **Page** object using the ID specified. + ##### Required parameters: + - **Page ID** #### 2. Create a page + This operation creates a new page in the specified database or as a child of an existing page. If the parent is a database, the property values of the new page in the properties parameter must conform to the parent database's property schema. If the parent is a page, the only valid property is title. + ##### Parameters: + - **Page ID** - **Properties** : Property values of this page - **Icon type** : Currently notion api accepts two icon options, emoji, external URL @@ -138,8 +143,11 @@ This operation creates a new page in the specified database or as a child of an - **Cover value** : Value of selected cover type #### 3. Update a page + This operation updates page property values for the specified page. Properties that are not set via the properties parameter will remain unchanged. + ##### Parameters: + - **Page ID** - **Parent type**: A database parent or page parent - **Properties** : Property values of this page @@ -150,77 +158,94 @@ This operation updates page property values for the specified page. Properties t - **Cover value** : Value of selected cover type #### 4. Retrieve a page property item + This operation retrieves a property_item object for a given page ID and property ID. Depending on the property type, the object returned will either be a value or a paginated list of property item values. See Property item objects for specifics. + ##### Parameters: + - **Page ID** - **Property ID** - **Limit** - **Start cursor** #### 5. Archive (delete) a page + ##### Required parameters: + - **Page ID** - **Archive**: Dropdown for archive and un archive the page ### Blocks + The following operations can be performed on the block resource: + - **[Retrieve a block](#1-retrieve-a-block)** - **[Append block children](#2-append-new-block-children)** - **[Retrieve block children](#3-retrieve-block-children)** - **[Update a block](#4-update-a-block)** - **[Delete a block](#5-delete-a-block)** - notion block - :::info To get the id for blocks, simply click on the menu icon for the block and click "Copy link". Afterwards, paste the link in the browser and it should look like this: `https://www.notion.so/Creating-Page-Sample-ee18b8779ae54f358b09221d6665ee15#7fcb3940a1264aadb2ad4ee9ffe11b0e` the string after **#** is the block id i.e. `7fcb3940a1264aadb2ad4ee9ffe11b0e`. ::: #### 1. Retrieve a block + This operation retrieves a **Block** object using the ID specified. ##### Required parameters: + - **Block ID** #### 2. Append new block children + This operation creates and appends new children blocks to the parent block_id specified. ##### Required parameters: + - **Block ID** - **Children**: Array of block objects #### 3. Retrieve block children + This operation retrieves a paginated array of child block objects contained in the block using the ID specified. ##### Required parameters: + - **Block ID** - **Limit** - **Start cursor** #### 4. Update a block + This operation updates the content for the specified block_id based on the block type. ##### Required parameters: + - **Block ID** - **Properties**: The block object type value with the properties to be updated - **Archive** #### 5. Delete a block + ##### Required parameters: + - **Block ID** ### User + The following operations can be performed on the user notion resource: #### 1. Retrieve a user from current workspace -This operation retrieves a User using the ID specified. +This operation retrieves a User using the ID specified. notion user ##### Required parameters: + - **User ID** #### 2. Retrieve list of users of a workspace @@ -228,6 +253,7 @@ This operation retrieves a User using the ID specified. This operation returns a paginated list of Users for the workspace. ##### Required parameters: + - **Limit** - **Start cursor** diff --git a/docs/docs/data-sources/s3.md b/docs/docs/data-sources/s3.md index db8f35c780..60488139a9 100644 --- a/docs/docs/data-sources/s3.md +++ b/docs/docs/data-sources/s3.md @@ -9,7 +9,7 @@ ToolJet can connect to Amazon S3 buckets and perform various operation on them. ## Connection -To add a new S3 source, go to the **Datasources manager** on the left sidebar of the app editor and click on `Add datasource` button. Select **AWS S3** from the modal that pops up. +To add a new S3 source, go to the **Data sources manager** on the left sidebar of the app editor and click on `Add data source` button. Select **AWS S3** from the modal that pops up. ToolJet supports connecting to AWS S3 using **IAM credentials**, **AWS Instance Profile** or **AWS ARN Role**. @@ -155,5 +155,5 @@ The presigned URLs are useful if you want your user/customer to be able to uploa :::info -We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial **[here](https://blog.tooljet.com/building-an-app-to-view-and-upload-files-in-aws-s3-bucket/)**. +We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial **[here](https://blog.tooljet.com/build-an-aws-s3-broswer-with-tooljet/)**. ::: diff --git a/docs/docs/widgets/html.md b/docs/docs/widgets/html.md index 41908bef43..cc1e06c574 100644 --- a/docs/docs/widgets/html.md +++ b/docs/docs/widgets/html.md @@ -1,9 +1,9 @@ --- id: html -title: HTML +title: HTML Viewer --- -# HTML +# HTML Viewer HTML widget can be used to create your own HTML-CSS layout. diff --git a/docs/docs/widgets/rich-text-editor.md b/docs/docs/widgets/rich-text-editor.md index 7c5ba592d7..9d99fb99e3 100644 --- a/docs/docs/widgets/rich-text-editor.md +++ b/docs/docs/widgets/rich-text-editor.md @@ -1,8 +1,8 @@ --- id: rich-text-editor -title: Rich Text Editor +title: Text Editor --- -# Rich Text Editor +# Text Editor Rich Text Editor can be used to enter and edit the text in HTML format. It should be preferred for blog posts, forum posts or notes sections. The text is to be used as the label for the radio button. diff --git a/docs/versioned_docs/version-2.18.0/data-sources/appwrite.md b/docs/versioned_docs/version-2.18.0/data-sources/appwrite.md index dc11572625..a0562cc980 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/appwrite.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/appwrite.md @@ -1,11 +1,11 @@ --- id: appwrite -title: Appwrite Database +title: Appwrite --- -# Appwrite Database +# Appwrite -Now build applications on top of your Appwrite database. +ToolJet can connect to appwrite database to read/write data. ## Connection @@ -20,7 +20,7 @@ You'll find the Secret key and other credentials on your Appwrite's project sett You should also set the scope for access to a particular resource. Learn more about the **API keys and scopes** [here](https://appwrite.io/docs/keys). ::: -To connect Appwrite datasource to your ToolJet application, go to the data source manager on the left-sidebar and click on the `+` button. Select Appwrite from the list of available datasources, provide the credentials and click **Save**. It is recommended to check the connection by clicking on 'Test connection' button to verify if the service account can access Appwrite from the ToolJet server. +To establish a connection with the Appwrite data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard.
diff --git a/docs/versioned_docs/version-2.18.0/data-sources/bigquery.md b/docs/versioned_docs/version-2.18.0/data-sources/bigquery.md index e3e29b8d4c..2b6eb57145 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/bigquery.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/bigquery.md @@ -9,15 +9,13 @@ ToolJet can connect to BigQuery databases to run BigQuery queries. ## Connection -Please refer [this](https://cloud.google.com/bigquery/docs/bigquery-web-ui) link to enable BigQuery API in Google Cloud Console. +To connect to BigQuery, you need to enable BigQuery API in your Google Cloud Console. You can follow the steps to enable BigQuery API from [this link](https://cloud.google.com/bigquery/docs/bigquery-web-ui). -Create **Service Account** and **key**, then get your configs downloaded as **JSON** +Next, you need to create a service account and generate a key for the same. You can follow the steps to create a service account from [this link](https://cloud.google.com/iam/docs/creating-managing-service-accounts). -To add a new BigQuery, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select BigQuery from the modal that pops up. +Now, copy and paste the data from the downloaded JSON file into the **Private key** field in the BigQuery data source form. -ToolJet requires the config json downloaded from your account to connect to BigQuery. Paste the json into the `Private key` field. - -**The json looks like**: +**The json file should look like this:** ```json { @@ -50,7 +48,6 @@ Click on `+` button of the query manager at the bottom panel of the editor and s BQ query -Click on the **run** button to run the query. NOTE: Query should be saved before running. :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) @@ -157,4 +154,4 @@ NOTE: visit -https://github.com/googleapis/nodejs-bigquery/blob/main/samples/cre ::: ### Delete Table -- To delete a table. \ No newline at end of file +- To delete a table. diff --git a/docs/versioned_docs/version-2.18.0/data-sources/cosmosdb.md b/docs/versioned_docs/version-2.18.0/data-sources/cosmosdb.md index a924932750..4b6d4f412f 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/cosmosdb.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/cosmosdb.md @@ -2,14 +2,14 @@ id: cosmosdb title: CosmosDB --- + # Cosmosdb -ToolJet can connect to CosmosDB databases to read and write data. - +ToolJet can connect to CosmosDB databases to read and write data. ## Connection -To add a new **[Azure Cosmos DB](https://docs.microsoft.com/en-us/javascript/api/overview/azure/cosmos-readme?view=azure-node-latest#key-concepts)**, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select CosmosDB from the modal that pops up. +To establish a connection with the CosmosDB data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. ToolJet requires the following to connect to your Cosmos DB. @@ -28,8 +28,7 @@ You can find the endpoint and key in the **[Azure Portal](https://portal.azure.c
- -## Supported queries: +## Supported Queries: - [Listing databases](#listing-databases) - [Listing containers](#listing-containers) @@ -38,54 +37,54 @@ You can find the endpoint and key in the **[Azure Portal](https://portal.azure.c - [Deleting an item](#deleting-an-item) - [Querying documents](#querying-documents) +### Listing Databases - - - -### Listing databases This query lists all the databases in a Cosmos DB. -### Listing containers +### Listing Containers + This query lists all the containers of a database in a Cosmos DB. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | +| Fields | Description | +| -------- | ------------------ | +| database | id of the database | + +### Inserting Item(s) -### Inserting item(s) This query inserts one or more items in a container of a database in a Cosmos DB. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| items | items to be inserted. Example: `{{[{name: "one", val: 1}, {name:"two", val: 2}]}}` | +| Fields | Description | +| --------- | ---------------------------------------------------------------------------------- | +| database | id of the database | +| container | id of the container | +| items | items to be inserted. Example: `{{[{name: "one", val: 1}, {name:"two", val: 2}]}}` | + +### Retrieving An item -### Retrieving an item To read a single item from a container of a database in a Cosmos DB, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| item | id of the item | +| Fields | Description | +| --------- | ------------------- | +| database | id of the database | +| container | id of the container | +| item | id of the item | +### Deleting An Item -### Deleting an item To delete an item from a container of a database in a Cosmos DB, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| item | id of the item | +| Fields | Description | +| --------- | ------------------- | +| database | id of the database | +| container | id of the container | +| item | id of the item | +### Querying Documents -### Querying documents To query documents from a container of a database in a Cosmos DB using SQL-like syntax, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| query | query to be executed. Example: `SELECT * FROM c WHERE c.age > 20 AND c.age <= 30` | +| Fields | Description | +| --------- | --------------------------------------------------------------------------------- | +| database | id of the database | +| container | id of the container | +| query | query to be executed. Example: `SELECT * FROM c WHERE c.age > 20 AND c.age <= 30` | diff --git a/docs/versioned_docs/version-2.18.0/data-sources/couchdb.md b/docs/versioned_docs/version-2.18.0/data-sources/couchdb.md index 385ed788ad..d4a6c70bd8 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/couchdb.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/couchdb.md @@ -8,27 +8,24 @@ title: CouchDB ToolJet can connect to CouchDB databases to read and write data. CocuhDB uses basic auth for authentication , username and password for the database is required to create an CouchDB data source on ToolJet. For more info visit [CouchDB docs](https://docs.couchdb.org/en/stable/). -Couch auth +## Supported Queries: - -## Supported queries: - -- [Listing records](#listing-records) -- [Retrieving a record](#retrieving-a-record) -- [Creating a record](#creating-a-record) -- [Updating a record](#updating-a-record) -- [Deleting a record](#deleting-a-record) +- [Listing Records](#listing-records) +- [Retrieving a Record](#retrieving-a-record) +- [Creating a Record](#creating-a-record) +- [Updating a Record](#updating-a-record) +- [Deleting a Record](#deleting-a-record) - [Find](#find) -- [Retrieving a view](#retrieving-a-view) +- [Retrieving a View](#retrieving-a-view) :::info NOTE: Record ID is same as document ID("_id") . ::: -### Listing records +### Listing Records This query lists all the records in a database. -#### Optional parameters: +#### Optional Parameters: - **Include docs** - **Descending order** @@ -83,9 +80,9 @@ Example response from CouchDb: } ``` -### Retrieving a record +### Retrieving a Record -#### Required parameters: +#### Required Parameters: - **Record ID** @@ -108,7 +105,7 @@ Example response from CouchDb: The returned JSON is the JSON of the document, including the document ID and revision number: -### Creating a record +### Creating a Record Couch create view @@ -122,10 +119,6 @@ The returned JSON is the JSON of the document, including the document ID and rev Click on the `run` button to run the query. -:::info -NOTE: Query must be saved before running. -::: - Example response from CouchDb: ```json @@ -137,12 +130,12 @@ Example response from CouchDb: ``` -### Updating a record +### Updating a Record You can get the revision id value, by sending a GET request to get the document details. You get the document as JSON in the response. For each update to the document, the revision field "_rev" gets changed. -#### Required parameters: +#### Required Parameters: - **Revision ID** - **Record ID** @@ -150,7 +143,7 @@ You get the document as JSON in the response. For each update to the document, t Couch update view -#### Example body: +#### Example Body: ```json [{"name":"tooljet"}] @@ -172,9 +165,9 @@ Example response from CouchDb: } ``` -### Deleting a record +### Deleting a Record -#### Required parameters: +#### Required Parameters: - **Revision ID** - **Record ID** @@ -200,7 +193,7 @@ Example response from CouchDb: Find documents using a declarative JSON querying syntax. -#### Required parameters: +#### Required Parameters: - **Selector** :::info @@ -212,7 +205,7 @@ selector syntax: https://pouchdb.com/guides/mango-queries.html Couch find -#### Example body: +#### Example Body: ```json { @@ -239,11 +232,11 @@ Example response from CouchDb: Couch find response -### Retrieving a view +### Retrieving a View Views are the primary tool used for querying and reporting on CouchDB documents. -#### Required parameters: +#### Required Parameters: - **View url** Reference for view :https://docs.couchdb.org/en/3.2.0/ddocs/views/intro.html#what-is-a-view @@ -252,7 +245,7 @@ Reference for view :https://docs.couchdb.org/en/3.2.0/ddocs/views/intro.html#wha Couch get view -#### Optional parameters: +#### Optional Parameters: - **Start key** - **End key** diff --git a/docs/versioned_docs/version-2.18.0/data-sources/dynamodb.md b/docs/versioned_docs/version-2.18.0/data-sources/dynamodb.md index abaacaa48c..127a917898 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/dynamodb.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/dynamodb.md @@ -8,7 +8,7 @@ DynamoDB is a managed non-relational database service provided by Amazon. ToolJe ## Connection -To establish a connection with the DynamoDB global datasource, you can either click on the `+Add new global datasource` button located on the query panel or navigate to the **[Global Datasources](/docs/data-sources/overview)** page through the ToolJet dashboard. +To establish a connection with the DynamoDB data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data sources](/docs/data-sources/overview)** page through the ToolJet dashboard.
diff --git a/docs/versioned_docs/version-2.18.0/data-sources/elasticsearch.md b/docs/versioned_docs/version-2.18.0/data-sources/elasticsearch.md index 3f06fc7b3d..6c996df16e 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/elasticsearch.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/elasticsearch.md @@ -9,7 +9,7 @@ ToolJet can connect to your Elasticsearch cluster to read and write data. ## Connection Please make sure the host/IP of the Elasticsearch cluster is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please **whitelist our IP**. -To add a new Elasticsearch database, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select Elasticsearch from the modal that pops up. +To establish a connection with the ElasticSearch data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. ToolJet requires the following to connect to your Elasticsearch cluster: - **Host** @@ -24,7 +24,7 @@ ToolJet requires the following to connect to your Elasticsearch cluster:
-Elastic search datasource is also providing an option for connecting services with ssl certificates. +Elastic search data source is also providing an option for connecting services with ssl certificates. - You can either use CA / Client certificates option. Elastic ssl diff --git a/docs/versioned_docs/version-2.18.0/data-sources/firestore.md b/docs/versioned_docs/version-2.18.0/data-sources/firestore.md index c0375c29c6..a498d8a75e 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/firestore.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/firestore.md @@ -4,13 +4,14 @@ title: Cloud Firestore --- # Cloud Firestore +ToolJet can connect to Cloud Firestore databases to read and write data. ## Connection -ToolJet connects to your Cloud Firestore using JSON key of your GCP service account. -To generate a new key, check out [Firestore's official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console). +ToolJet connects to your Cloud Firestore using JSON key of your GCP service account. Get your service account key as JSON from GCP console. For generating a new key, check out [Firestore's official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console). -Once the key is downloaded, click on `+` button of data sources panel at the left-bottom corner of the app editor. Select Firestore from the modal that pops up. Paste the key in the field for GCP key. Click on **Test connection** button to verify if the service account can access Firestore from ToolJet server. Click on **Save** button to save the datasource. +Once you have the key, open it in a text editor and copy the contents. Paste the contents in the **Private key** field of the Firestore data source modal. +Click on **Test connection** button to verify if the key is valid. Click on **Save** button to save the data source. firestore add ds @@ -160,4 +161,4 @@ The Firestore query result is in the form of object so we’ll need to transform ```js return data = Array(data) -``` \ No newline at end of file +``` diff --git a/docs/versioned_docs/version-2.18.0/data-sources/gcs.md b/docs/versioned_docs/version-2.18.0/data-sources/gcs.md index 6483a43408..03cad29535 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/gcs.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/gcs.md @@ -9,19 +9,18 @@ ToolJet can connect to GCS buckets and perform various operation on them. ## Supported operations --**Read file** --**Upload file** --**List buckets** --**List files in a bucket** --**Signed url for download** --**Signed url for upload** +- **Read file** +- **Upload file** +- **List buckets** +- **List files in a bucket** +- **Signed url for download** +- **Signed url for upload** ## Connection -To add a new GCS source, click on the **Add or edit datasource** icon on the left sidebar of the app editor and click on `Add datasource` button. Select GCS from the modal that pops up. +To establish a connection with the Google Cloud Storage data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. -ToolJet requires the **json private key** of a service account to be able to connect to GCS. -You can follow the [google documentation](https://cloud.google.com/docs/authentication/getting-started) to get started. +To connect to GCS, you need to provide the JSON Private Key of a service account that has access to the bucket. You can follow the [google documentation](https://cloud.google.com/docs/authentication/getting-started) to get started. gcs connection diff --git a/docs/versioned_docs/version-2.18.0/data-sources/mailgun.md b/docs/versioned_docs/version-2.18.0/data-sources/mailgun.md index 6b4c71ca78..09907b82d1 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/mailgun.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/mailgun.md @@ -15,7 +15,7 @@ The Mailgun API Datasource supports for interaction with the mail endpoint of th ## Connection -To add a new Mailgun API datasource, click the **Datasource manager** icon on the left-sidebar of the app builder and click on the `Add datasource` button, then select Mailgun API from the modal that pops up. +To establish a connection with the MailGun data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. Enter your **Mailgun API key** in the "API key" field. @@ -58,7 +58,3 @@ For example: `admin@tooljet.io` **Send multiple individual emails to multiple recipients** - set Multiple recipients field to `{{true}}` and the `Send mail to` field will be split into multiple emails and send to each recipient. ::: - -:::note -NOTE: Query should be saved before running. -::: diff --git a/docs/versioned_docs/version-2.18.0/data-sources/minio.md b/docs/versioned_docs/version-2.18.0/data-sources/minio.md index 3f19ffc32d..0ac53fd17d 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/minio.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/minio.md @@ -20,7 +20,7 @@ ToolJet can connect to minio and perform various operation on them. ## Connection -To add a new minio source, click on the **Add or edit datasource** icon on the left sidebar of the app editor and click on `Add datasource` button. Select Minio from the modal that pops up. +To establish a connection with the Minio data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your DynamoDB: @@ -44,7 +44,7 @@ Click on `+` button of the **query manager** at the bottom panel of the editor a miniIo query Click on the **run** button to run the query. -**NOTE**: Query should be saved before running. + :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) diff --git a/docs/versioned_docs/version-2.18.0/data-sources/mongodb.md b/docs/versioned_docs/version-2.18.0/data-sources/mongodb.md index 0ae21b519a..8a4dea7b16 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/mongodb.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/mongodb.md @@ -11,7 +11,7 @@ ToolJet can connect to MongoDB to read and write data. Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. -To add a new MongoDB, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select MongoDB from the modal that pops up. +To establish a connection with the MongoDB data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your MongoDB. @@ -32,15 +32,14 @@ Click on `+` button of the query manager at the bottom panel of the editor and s ToolJet - Mongo query - - -Click on the 'run' button to run the query. NOTE: Query should be saved before running. +Click on the 'run' button to run the query. :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) ::: ### Supported operations + - [List Collections](#list-collections) - [Find One](#find-one) - [Find Many](#find-many) @@ -59,51 +58,89 @@ Query results can be transformed using transformations. Read our transformations - [Delete One](#delete-one) - [Delete Many](#delete-many) - [Bulk Operations](#bulk-operations) + #### List Collections + Returns list of collections + #### Fine One + Return a document which satisfy the given filter and options. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/findOne) + #### Fine Many + Return list of documents which satisfy the given filter and options. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/find/) + #### Total Count + Returns an estimation of the number of documents in the collection based on collection metadata. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#estimateddocumentcount) + #### Count + Returns the number of documents based on the filter. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#countdocuments) + #### Distinct + Retrieve a list of distinct values for a field based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/distinct/) + #### Insert One + Insert a document. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/insertOne/) + #### Insert Many + Insert list of documents. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/insertMany/) + #### Update One + Update a document based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/updateOne/) + #### Update Many + Update many documents based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/updateMany/) + #### Replace One + Replace a document based on filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/replaceOne/) + #### Find One and Update + If your application requires the document after updating, use this instead of `Update One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneandupdate) + #### Find One and Replace + If your application requires the document after updating, use this instead of `Replace One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneandreplace) + #### Find One and Delete + If your application requires the document after deleting, use this instead of `Delete One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneanddelete) + #### Aggregate + Aggregation operations are expressions you can use to produce reduced and summarized results. [Reference](https://docs.mongodb.com/drivers/node/v4.0/fundamentals/aggregation/) + #### Delete One + Delete a record based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/deleteOne/) + #### Delete Many + Delete many records based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/deleteMany/) + #### Bulk Operations + Perform bulk operations. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/bulkWrite/) ### Dynamic Quries + ```javascript { amount: { $lt: '{{ components.textinput1.value }}' }} -// Dates +// Dates // supported: Extended JSON syntax { createdAt: { $date: '{{ new Date('01/10/2020') }}'} } // not supported: MongoDB classic syntax { createdAt: new Date('01/10/2020') } ``` + Reference on [mongodb extended JSON](https://docs.mongodb.com/manual/reference/mongodb-extended-json/) supported data types diff --git a/docs/versioned_docs/version-2.18.0/data-sources/mssql.md b/docs/versioned_docs/version-2.18.0/data-sources/mssql.md index a8aa169ce5..0f9fa32b2f 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/mssql.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/mssql.md @@ -12,7 +12,7 @@ ToolJet can connect to MS SQL Server & Azure SQL databases to read and write dat Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. -To add new MS SQL Server / Azure SQL database, click on the '+' button on data sources panel at the left-bottom corner of the app editor. Select `SQL Server` from the modal that pops up. +To establish a connection with the MS SQL Server data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your PostgreSQL database. @@ -32,7 +32,7 @@ Click on 'Test connection' button to verify if the credentials are correct and t ## Querying SQL Server / Azure SQL databases Click on '+' button of the query manager at the bottom panel of the editor and select the database added in the previous step as the data source. -Click on the 'run' button to run the query. NOTE: Query should be saved before running. +Click on the 'run' button to run the query. ToolJet - Redis connection diff --git a/docs/versioned_docs/version-2.18.0/data-sources/mysql.md b/docs/versioned_docs/version-2.18.0/data-sources/mysql.md index 63af848971..c349d83171 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/mysql.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/mysql.md @@ -7,11 +7,11 @@ ToolJet can connect to MySQL databases to read and write data. ## Connection -To establish a connection with the MySQL datasource, you can either click on the `+Add New` button located on the query panel or navigate to the **[Global Datasources](/docs/data-sources/overview)** page through the ToolJet dashboard. +To establish a connection with the MySQL data source, you can either click on the `+Add New` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard.
-MySQL datasource +MySQL data source

@@ -20,31 +20,30 @@ To establish a connection with the MySQL datasource, you can either click on the Please make sure the **Host/IP** of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please **whitelist** our IP. ::: - **ToolJet requires the following to connect to your MySQL database:** -| Parameter | Description | -|:--- |:--- | -| Username | Username of the MySQL database | -| Password | Password of the MySQL database | -| Database name | Name of the MySQL database | +| Parameter | Description | +| :-------------- | :------------------------------------------------------------------------ | +| Username | Username of the MySQL database | +| Password | Password of the MySQL database | +| Database name | Name of the MySQL database | | Connection type | Connection type of the MySQL database: either **Hostname** or **Socket**. | If you are using **Hostname** as the connection type, you will need to provide the following information: -| Parameter | Description | -|:--- |:--- | -| Host/IP | Hostname or IP address of the MySQL database | -| Port | Port number of the MySQL database | -| SSL | Enable SSL connection to the MySQL database | +| Parameter | Description | +| :-------- | :------------------------------------------- | +| Host/IP | Hostname or IP address of the MySQL database | +| Port | Port number of the MySQL database | +| SSL | Enable SSL connection to the MySQL database | If you are using **Socket** as the connection type, you will need to provide the following information: -| Parameter | Description | -|:--- |:--- | +| Parameter | Description | +| :---------- | :---------------------- | | Socket path | Path of the socket file | -It is recommended to create a new MySQL database user so that you can control the access levels of ToolJet. +It is recommended to create a new MySQL database user so that you can control the access levels of ToolJet.
@@ -63,9 +62,10 @@ Once the MySQL data source is added, you can create queries to read and write da ### SQL mode -SQL mode can be used to query MySQL database using SQL queries. Select SQL mode from the dropdown and then enter the SQL query in the editor. +SQL mode can be used to query MySQL database using SQL queries. Select SQL mode from the dropdown and then enter the SQL query in the editor. **Example:** + ```sql SELECT * FROM users ``` @@ -80,7 +80,8 @@ SELECT * FROM users GUI mode can be used to query MySQL database without writing queries. Select GUI mode from the dropdown and then choose the operation **Bulk update using primary key**. Enter the **Table** name and **Primary key column** name. Now, in the editor enter the records in the form of an array of objects. Each object should contain the primary key column and the columns to be updated. -**Example:** +**Example:** + ```json {{ [ {id: 1, channel: 33}, {id:2, channel:24} ] }} ``` @@ -93,4 +94,4 @@ GUI mode can be used to query MySQL database without writing queries. Select GUI :::tip Query results can be transformed using transformations. Learn more about transformations [here](/docs/tutorial/transformations). -::: \ No newline at end of file +::: diff --git a/docs/versioned_docs/version-2.18.0/data-sources/notion.md b/docs/versioned_docs/version-2.18.0/data-sources/notion.md index 88000f3aa6..a224116642 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/notion.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/notion.md @@ -2,11 +2,15 @@ id: notion title: Notion --- + # Notion ToolJet can connect to a Notion workspace to do operations on notion pages, databases and blocks. + ## Connection +To establish a connection with the Notion data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. + For integrating Notion with ToolJet we will need the API token. The API token can be generated from your Notion workspace settings. Read the official Notion docs for [Creating an internal integration with notion API](https://www.notion.so/help/create-integrations-with-the-notion-api).
@@ -18,37 +22,33 @@ For integrating Notion with ToolJet we will need the API token. The API token ca ## Querying Notion Notion API provides support for: + - **[Database](#database)** - **[Page](#page)** - **[Block](#blocks)** - **[User](#user)** - notion querying - :::tip Before querying Notion, you must share the database with your integration. Click the share button in your database view, find your integration name select it. - notion share - ::: ### Database On database resource you can perform the following operations: + - **[Retrieve a database](#1-retrieve-a-database)** - **[Query a database](#2-query-a-database)** - **[Create a database](#3-create-a-database)** - **[Update a database](#4-update-a-database)** - notion db - #### 1. Retrieve a database This operations retrieves a Database object using the ID specified. @@ -57,13 +57,12 @@ This operations retrieves a Database object using the ID specified. - **Database ID**: You'll find the Database ID in the url. Suppose this is the example url: `https://www.notion.so/workspace/XXX?v=YYY&p=ZZZ` then `XXX` is the database ID, `YYY` is the view ID and `ZZZ` is the page ID. - notion db retreieve - #### 2. Query a database This operation gets a list of **Pages** contained in the database, filtered and ordered according to the filter conditions and sort criteria provided in the query. + ##### Required parameters: - **Database ID** : You'll find the Database ID in the url. Suppose this is the example url: `https://www.notion.so/workspace/XXX?v=YYY&p=ZZZ` then `XXX` is the database ID, `YYY` is the view ID and `ZZZ` is the page ID. @@ -98,6 +97,7 @@ This operation creates a database as a subpage in the specified parent page, wit This operation updates an existing database as specified by the parameters. ##### Required parameters: + - **Database ID** ##### Optional parameters: @@ -112,24 +112,29 @@ This operation updates an existing database as specified by the parameters. ### Page On page resource you can perform the following operations: + - **[Retrieve a page](#1-retrieve-a-page)** - **[Create a page](#2-create-a-page)** - **[Update a page](#3-update-a-page)** - **[Retrieve a page property](#4-retrieve-a-page-property-item)** - **[Archive a page](#5-archive-delete-a-page)** - notion page - #### 1. Retrieve a page + This operation retrieves a **Page** object using the ID specified. + ##### Required parameters: + - **Page ID** #### 2. Create a page + This operation creates a new page in the specified database or as a child of an existing page. If the parent is a database, the property values of the new page in the properties parameter must conform to the parent database's property schema. If the parent is a page, the only valid property is title. + ##### Parameters: + - **Page ID** - **Properties** : Property values of this page - **Icon type** : Currently notion api accepts two icon options, emoji, external URL @@ -138,8 +143,11 @@ This operation creates a new page in the specified database or as a child of an - **Cover value** : Value of selected cover type #### 3. Update a page + This operation updates page property values for the specified page. Properties that are not set via the properties parameter will remain unchanged. + ##### Parameters: + - **Page ID** - **Parent type**: A database parent or page parent - **Properties** : Property values of this page @@ -150,77 +158,94 @@ This operation updates page property values for the specified page. Properties t - **Cover value** : Value of selected cover type #### 4. Retrieve a page property item + This operation retrieves a property_item object for a given page ID and property ID. Depending on the property type, the object returned will either be a value or a paginated list of property item values. See Property item objects for specifics. + ##### Parameters: + - **Page ID** - **Property ID** - **Limit** - **Start cursor** #### 5. Archive (delete) a page + ##### Required parameters: + - **Page ID** - **Archive**: Dropdown for archive and un archive the page ### Blocks + The following operations can be performed on the block resource: + - **[Retrieve a block](#1-retrieve-a-block)** - **[Append block children](#2-append-new-block-children)** - **[Retrieve block children](#3-retrieve-block-children)** - **[Update a block](#4-update-a-block)** - **[Delete a block](#5-delete-a-block)** - notion block - :::info To get the id for blocks, simply click on the menu icon for the block and click "Copy link". Afterwards, paste the link in the browser and it should look like this: `https://www.notion.so/Creating-Page-Sample-ee18b8779ae54f358b09221d6665ee15#7fcb3940a1264aadb2ad4ee9ffe11b0e` the string after **#** is the block id i.e. `7fcb3940a1264aadb2ad4ee9ffe11b0e`. ::: #### 1. Retrieve a block + This operation retrieves a **Block** object using the ID specified. ##### Required parameters: + - **Block ID** #### 2. Append new block children + This operation creates and appends new children blocks to the parent block_id specified. ##### Required parameters: + - **Block ID** - **Children**: Array of block objects #### 3. Retrieve block children + This operation retrieves a paginated array of child block objects contained in the block using the ID specified. ##### Required parameters: + - **Block ID** - **Limit** - **Start cursor** #### 4. Update a block + This operation updates the content for the specified block_id based on the block type. ##### Required parameters: + - **Block ID** - **Properties**: The block object type value with the properties to be updated - **Archive** #### 5. Delete a block + ##### Required parameters: + - **Block ID** ### User + The following operations can be performed on the user notion resource: #### 1. Retrieve a user from current workspace -This operation retrieves a User using the ID specified. +This operation retrieves a User using the ID specified. notion user ##### Required parameters: + - **User ID** #### 2. Retrieve list of users of a workspace @@ -228,6 +253,7 @@ This operation retrieves a User using the ID specified. This operation returns a paginated list of Users for the workspace. ##### Required parameters: + - **Limit** - **Start cursor** diff --git a/docs/versioned_docs/version-2.18.0/data-sources/s3.md b/docs/versioned_docs/version-2.18.0/data-sources/s3.md index db8f35c780..60488139a9 100644 --- a/docs/versioned_docs/version-2.18.0/data-sources/s3.md +++ b/docs/versioned_docs/version-2.18.0/data-sources/s3.md @@ -9,7 +9,7 @@ ToolJet can connect to Amazon S3 buckets and perform various operation on them. ## Connection -To add a new S3 source, go to the **Datasources manager** on the left sidebar of the app editor and click on `Add datasource` button. Select **AWS S3** from the modal that pops up. +To add a new S3 source, go to the **Data sources manager** on the left sidebar of the app editor and click on `Add data source` button. Select **AWS S3** from the modal that pops up. ToolJet supports connecting to AWS S3 using **IAM credentials**, **AWS Instance Profile** or **AWS ARN Role**. @@ -155,5 +155,5 @@ The presigned URLs are useful if you want your user/customer to be able to uploa :::info -We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial **[here](https://blog.tooljet.com/building-an-app-to-view-and-upload-files-in-aws-s3-bucket/)**. +We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial **[here](https://blog.tooljet.com/build-an-aws-s3-broswer-with-tooljet/)**. ::: diff --git a/docs/versioned_docs/version-2.18.0/widgets/html.md b/docs/versioned_docs/version-2.18.0/widgets/html.md index 41908bef43..cc1e06c574 100644 --- a/docs/versioned_docs/version-2.18.0/widgets/html.md +++ b/docs/versioned_docs/version-2.18.0/widgets/html.md @@ -1,9 +1,9 @@ --- id: html -title: HTML +title: HTML Viewer --- -# HTML +# HTML Viewer HTML widget can be used to create your own HTML-CSS layout. diff --git a/docs/versioned_docs/version-2.18.0/widgets/rich-text-editor.md b/docs/versioned_docs/version-2.18.0/widgets/rich-text-editor.md index 7c5ba592d7..9d99fb99e3 100644 --- a/docs/versioned_docs/version-2.18.0/widgets/rich-text-editor.md +++ b/docs/versioned_docs/version-2.18.0/widgets/rich-text-editor.md @@ -1,8 +1,8 @@ --- id: rich-text-editor -title: Rich Text Editor +title: Text Editor --- -# Rich Text Editor +# Text Editor Rich Text Editor can be used to enter and edit the text in HTML format. It should be preferred for blog posts, forum posts or notes sections. The text is to be used as the label for the radio button. diff --git a/docs/versioned_docs/version-2.19.0/data-sources/appwrite.md b/docs/versioned_docs/version-2.19.0/data-sources/appwrite.md index dc11572625..c987afdd6b 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/appwrite.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/appwrite.md @@ -1,11 +1,11 @@ --- id: appwrite -title: Appwrite Database +title: Appwrite --- -# Appwrite Database +# Appwrite -Now build applications on top of your Appwrite database. +ToolJet can connect to appwrite database to read/write data. ## Connection @@ -20,7 +20,7 @@ You'll find the Secret key and other credentials on your Appwrite's project sett You should also set the scope for access to a particular resource. Learn more about the **API keys and scopes** [here](https://appwrite.io/docs/keys). ::: -To connect Appwrite datasource to your ToolJet application, go to the data source manager on the left-sidebar and click on the `+` button. Select Appwrite from the list of available datasources, provide the credentials and click **Save**. It is recommended to check the connection by clicking on 'Test connection' button to verify if the service account can access Appwrite from the ToolJet server. +To establish a connection with the Appwrite data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard.
diff --git a/docs/versioned_docs/version-2.19.0/data-sources/bigquery.md b/docs/versioned_docs/version-2.19.0/data-sources/bigquery.md index e3e29b8d4c..2b6eb57145 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/bigquery.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/bigquery.md @@ -9,15 +9,13 @@ ToolJet can connect to BigQuery databases to run BigQuery queries. ## Connection -Please refer [this](https://cloud.google.com/bigquery/docs/bigquery-web-ui) link to enable BigQuery API in Google Cloud Console. +To connect to BigQuery, you need to enable BigQuery API in your Google Cloud Console. You can follow the steps to enable BigQuery API from [this link](https://cloud.google.com/bigquery/docs/bigquery-web-ui). -Create **Service Account** and **key**, then get your configs downloaded as **JSON** +Next, you need to create a service account and generate a key for the same. You can follow the steps to create a service account from [this link](https://cloud.google.com/iam/docs/creating-managing-service-accounts). -To add a new BigQuery, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select BigQuery from the modal that pops up. +Now, copy and paste the data from the downloaded JSON file into the **Private key** field in the BigQuery data source form. -ToolJet requires the config json downloaded from your account to connect to BigQuery. Paste the json into the `Private key` field. - -**The json looks like**: +**The json file should look like this:** ```json { @@ -50,7 +48,6 @@ Click on `+` button of the query manager at the bottom panel of the editor and s BQ query -Click on the **run** button to run the query. NOTE: Query should be saved before running. :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) @@ -157,4 +154,4 @@ NOTE: visit -https://github.com/googleapis/nodejs-bigquery/blob/main/samples/cre ::: ### Delete Table -- To delete a table. \ No newline at end of file +- To delete a table. diff --git a/docs/versioned_docs/version-2.19.0/data-sources/cosmosdb.md b/docs/versioned_docs/version-2.19.0/data-sources/cosmosdb.md index a924932750..9ffe9b3e6d 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/cosmosdb.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/cosmosdb.md @@ -2,14 +2,14 @@ id: cosmosdb title: CosmosDB --- + # Cosmosdb -ToolJet can connect to CosmosDB databases to read and write data. - +ToolJet can connect to CosmosDB databases to read and write data. ## Connection -To add a new **[Azure Cosmos DB](https://docs.microsoft.com/en-us/javascript/api/overview/azure/cosmos-readme?view=azure-node-latest#key-concepts)**, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select CosmosDB from the modal that pops up. +To establish a connection with the CosmosDB data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. ToolJet requires the following to connect to your Cosmos DB. @@ -28,8 +28,7 @@ You can find the endpoint and key in the **[Azure Portal](https://portal.azure.c
- -## Supported queries: +## Supported Queries: - [Listing databases](#listing-databases) - [Listing containers](#listing-containers) @@ -38,54 +37,54 @@ You can find the endpoint and key in the **[Azure Portal](https://portal.azure.c - [Deleting an item](#deleting-an-item) - [Querying documents](#querying-documents) +### Listing Databases - - - -### Listing databases This query lists all the databases in a Cosmos DB. -### Listing containers +### Listing Containers + This query lists all the containers of a database in a Cosmos DB. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | +| Fields | Description | +| -------- | ------------------ | +| database | id of the database | + +### Inserting Item(s) -### Inserting item(s) This query inserts one or more items in a container of a database in a Cosmos DB. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| items | items to be inserted. Example: `{{[{name: "one", val: 1}, {name:"two", val: 2}]}}` | +| Fields | Description | +| --------- | ---------------------------------------------------------------------------------- | +| database | id of the database | +| container | id of the container | +| items | items to be inserted. Example: `{{[{name: "one", val: 1}, {name:"two", val: 2}]}}` | + +### Retrieving An Item -### Retrieving an item To read a single item from a container of a database in a Cosmos DB, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| item | id of the item | +| Fields | Description | +| --------- | ------------------- | +| database | id of the database | +| container | id of the container | +| item | id of the item | +### Deleting An Item -### Deleting an item To delete an item from a container of a database in a Cosmos DB, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| item | id of the item | +| Fields | Description | +| --------- | ------------------- | +| database | id of the database | +| container | id of the container | +| item | id of the item | +### Querying Documents -### Querying documents To query documents from a container of a database in a Cosmos DB using SQL-like syntax, use the following query. -| Fields | description | -| ----------- | ----------- | -| database | id of the database | -| container | id of the container | -| query | query to be executed. Example: `SELECT * FROM c WHERE c.age > 20 AND c.age <= 30` | +| Fields | Description | +| --------- | --------------------------------------------------------------------------------- | +| database | id of the database | +| container | id of the container | +| query | query to be executed. Example: `SELECT * FROM c WHERE c.age > 20 AND c.age <= 30` | diff --git a/docs/versioned_docs/version-2.19.0/data-sources/couchdb.md b/docs/versioned_docs/version-2.19.0/data-sources/couchdb.md index 385ed788ad..3d09e49231 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/couchdb.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/couchdb.md @@ -13,22 +13,22 @@ ToolJet can connect to CouchDB databases to read and write data. CocuhDB uses ba ## Supported queries: -- [Listing records](#listing-records) -- [Retrieving a record](#retrieving-a-record) -- [Creating a record](#creating-a-record) -- [Updating a record](#updating-a-record) -- [Deleting a record](#deleting-a-record) +- [Listing Records](#listing-records) +- [Retrieving a Record](#retrieving-a-record) +- [Creating a Record](#creating-a-record) +- [Updating a Record](#updating-a-record) +- [Deleting a Record](#deleting-a-record) - [Find](#find) -- [Retrieving a view](#retrieving-a-view) +- [Retrieving a View](#retrieving-a-view) :::info NOTE: Record ID is same as document ID("_id") . ::: -### Listing records +### Listing Records This query lists all the records in a database. -#### Optional parameters: +#### Optional Parameters: - **Include docs** - **Descending order** @@ -83,9 +83,9 @@ Example response from CouchDb: } ``` -### Retrieving a record +### Retrieving a Record -#### Required parameters: +#### Required Parameters: - **Record ID** @@ -108,7 +108,7 @@ Example response from CouchDb: The returned JSON is the JSON of the document, including the document ID and revision number: -### Creating a record +### Creating a Record Couch create view @@ -122,10 +122,6 @@ The returned JSON is the JSON of the document, including the document ID and rev Click on the `run` button to run the query. -:::info -NOTE: Query must be saved before running. -::: - Example response from CouchDb: ```json @@ -137,12 +133,12 @@ Example response from CouchDb: ``` -### Updating a record +### Updating a Record You can get the revision id value, by sending a GET request to get the document details. You get the document as JSON in the response. For each update to the document, the revision field "_rev" gets changed. -#### Required parameters: +#### Required Parameters: - **Revision ID** - **Record ID** @@ -150,7 +146,7 @@ You get the document as JSON in the response. For each update to the document, t Couch update view -#### Example body: +#### Example Body: ```json [{"name":"tooljet"}] @@ -172,9 +168,9 @@ Example response from CouchDb: } ``` -### Deleting a record +### Deleting a Record -#### Required parameters: +#### Required Parameters: - **Revision ID** - **Record ID** @@ -200,7 +196,7 @@ Example response from CouchDb: Find documents using a declarative JSON querying syntax. -#### Required parameters: +#### Required Parameters: - **Selector** :::info @@ -212,7 +208,7 @@ selector syntax: https://pouchdb.com/guides/mango-queries.html Couch find -#### Example body: +#### Example Body: ```json { @@ -239,11 +235,11 @@ Example response from CouchDb: Couch find response -### Retrieving a view +### Retrieving a View Views are the primary tool used for querying and reporting on CouchDB documents. -#### Required parameters: +#### Required Parameters: - **View url** Reference for view :https://docs.couchdb.org/en/3.2.0/ddocs/views/intro.html#what-is-a-view @@ -252,7 +248,7 @@ Reference for view :https://docs.couchdb.org/en/3.2.0/ddocs/views/intro.html#wha Couch get view -#### Optional parameters: +#### Optional Parameters: - **Start key** - **End key** diff --git a/docs/versioned_docs/version-2.19.0/data-sources/dynamodb.md b/docs/versioned_docs/version-2.19.0/data-sources/dynamodb.md index abaacaa48c..127a917898 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/dynamodb.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/dynamodb.md @@ -8,7 +8,7 @@ DynamoDB is a managed non-relational database service provided by Amazon. ToolJe ## Connection -To establish a connection with the DynamoDB global datasource, you can either click on the `+Add new global datasource` button located on the query panel or navigate to the **[Global Datasources](/docs/data-sources/overview)** page through the ToolJet dashboard. +To establish a connection with the DynamoDB data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data sources](/docs/data-sources/overview)** page through the ToolJet dashboard.
diff --git a/docs/versioned_docs/version-2.19.0/data-sources/elasticsearch.md b/docs/versioned_docs/version-2.19.0/data-sources/elasticsearch.md index 3f06fc7b3d..6c996df16e 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/elasticsearch.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/elasticsearch.md @@ -9,7 +9,7 @@ ToolJet can connect to your Elasticsearch cluster to read and write data. ## Connection Please make sure the host/IP of the Elasticsearch cluster is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please **whitelist our IP**. -To add a new Elasticsearch database, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select Elasticsearch from the modal that pops up. +To establish a connection with the ElasticSearch data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. ToolJet requires the following to connect to your Elasticsearch cluster: - **Host** @@ -24,7 +24,7 @@ ToolJet requires the following to connect to your Elasticsearch cluster:
-Elastic search datasource is also providing an option for connecting services with ssl certificates. +Elastic search data source is also providing an option for connecting services with ssl certificates. - You can either use CA / Client certificates option. Elastic ssl diff --git a/docs/versioned_docs/version-2.19.0/data-sources/firestore.md b/docs/versioned_docs/version-2.19.0/data-sources/firestore.md index c0375c29c6..4b86322ea5 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/firestore.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/firestore.md @@ -4,12 +4,14 @@ title: Cloud Firestore --- # Cloud Firestore +ToolJet can connect to Cloud Firestore databases to read and write data. ## Connection -ToolJet connects to your Cloud Firestore using JSON key of your GCP service account. -To generate a new key, check out [Firestore's official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console). +ToolJet connects to your Cloud Firestore using JSON key of your GCP service account. Get your service account key as JSON from GCP console. For generating a new key, check out [Firestore's official documentation](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console). -Once the key is downloaded, click on `+` button of data sources panel at the left-bottom corner of the app editor. Select Firestore from the modal that pops up. Paste the key in the field for GCP key. Click on **Test connection** button to verify if the service account can access Firestore from ToolJet server. Click on **Save** button to save the datasource. +Once you have the key, open it in a text editor and copy the contents. Paste the contents in the **Private key** field of the Firestore data source modal. + +Click on **Test connection** button to verify if the key is valid. Click on **Save** button to save the data source. firestore add ds @@ -160,4 +162,4 @@ The Firestore query result is in the form of object so we’ll need to transform ```js return data = Array(data) -``` \ No newline at end of file +``` diff --git a/docs/versioned_docs/version-2.19.0/data-sources/gcs.md b/docs/versioned_docs/version-2.19.0/data-sources/gcs.md index 6483a43408..03cad29535 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/gcs.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/gcs.md @@ -9,19 +9,18 @@ ToolJet can connect to GCS buckets and perform various operation on them. ## Supported operations --**Read file** --**Upload file** --**List buckets** --**List files in a bucket** --**Signed url for download** --**Signed url for upload** +- **Read file** +- **Upload file** +- **List buckets** +- **List files in a bucket** +- **Signed url for download** +- **Signed url for upload** ## Connection -To add a new GCS source, click on the **Add or edit datasource** icon on the left sidebar of the app editor and click on `Add datasource` button. Select GCS from the modal that pops up. +To establish a connection with the Google Cloud Storage data source, you can either click on the `+Add new data source` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard. -ToolJet requires the **json private key** of a service account to be able to connect to GCS. -You can follow the [google documentation](https://cloud.google.com/docs/authentication/getting-started) to get started. +To connect to GCS, you need to provide the JSON Private Key of a service account that has access to the bucket. You can follow the [google documentation](https://cloud.google.com/docs/authentication/getting-started) to get started. gcs connection diff --git a/docs/versioned_docs/version-2.19.0/data-sources/mailgun.md b/docs/versioned_docs/version-2.19.0/data-sources/mailgun.md index 6b4c71ca78..09907b82d1 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/mailgun.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/mailgun.md @@ -15,7 +15,7 @@ The Mailgun API Datasource supports for interaction with the mail endpoint of th ## Connection -To add a new Mailgun API datasource, click the **Datasource manager** icon on the left-sidebar of the app builder and click on the `Add datasource` button, then select Mailgun API from the modal that pops up. +To establish a connection with the MailGun data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. Enter your **Mailgun API key** in the "API key" field. @@ -58,7 +58,3 @@ For example: `admin@tooljet.io` **Send multiple individual emails to multiple recipients** - set Multiple recipients field to `{{true}}` and the `Send mail to` field will be split into multiple emails and send to each recipient. ::: - -:::note -NOTE: Query should be saved before running. -::: diff --git a/docs/versioned_docs/version-2.19.0/data-sources/minio.md b/docs/versioned_docs/version-2.19.0/data-sources/minio.md index 3f19ffc32d..0ac53fd17d 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/minio.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/minio.md @@ -20,7 +20,7 @@ ToolJet can connect to minio and perform various operation on them. ## Connection -To add a new minio source, click on the **Add or edit datasource** icon on the left sidebar of the app editor and click on `Add datasource` button. Select Minio from the modal that pops up. +To establish a connection with the Minio data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your DynamoDB: @@ -44,7 +44,7 @@ Click on `+` button of the **query manager** at the bottom panel of the editor a miniIo query Click on the **run** button to run the query. -**NOTE**: Query should be saved before running. + :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) diff --git a/docs/versioned_docs/version-2.19.0/data-sources/mongodb.md b/docs/versioned_docs/version-2.19.0/data-sources/mongodb.md index 0ae21b519a..8a4dea7b16 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/mongodb.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/mongodb.md @@ -11,7 +11,7 @@ ToolJet can connect to MongoDB to read and write data. Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. -To add a new MongoDB, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select MongoDB from the modal that pops up. +To establish a connection with the MongoDB data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your MongoDB. @@ -32,15 +32,14 @@ Click on `+` button of the query manager at the bottom panel of the editor and s ToolJet - Mongo query - - -Click on the 'run' button to run the query. NOTE: Query should be saved before running. +Click on the 'run' button to run the query. :::tip Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations) ::: ### Supported operations + - [List Collections](#list-collections) - [Find One](#find-one) - [Find Many](#find-many) @@ -59,51 +58,89 @@ Query results can be transformed using transformations. Read our transformations - [Delete One](#delete-one) - [Delete Many](#delete-many) - [Bulk Operations](#bulk-operations) + #### List Collections + Returns list of collections + #### Fine One + Return a document which satisfy the given filter and options. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/findOne) + #### Fine Many + Return list of documents which satisfy the given filter and options. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/find/) + #### Total Count + Returns an estimation of the number of documents in the collection based on collection metadata. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#estimateddocumentcount) + #### Count + Returns the number of documents based on the filter. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#countdocuments) + #### Distinct + Retrieve a list of distinct values for a field based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/distinct/) + #### Insert One + Insert a document. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/insertOne/) + #### Insert Many + Insert list of documents. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/insertMany/) + #### Update One + Update a document based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/updateOne/) + #### Update Many + Update many documents based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/updateMany/) + #### Replace One + Replace a document based on filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/replaceOne/) + #### Find One and Update + If your application requires the document after updating, use this instead of `Update One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneandupdate) + #### Find One and Replace + If your application requires the document after updating, use this instead of `Replace One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneandreplace) + #### Find One and Delete + If your application requires the document after deleting, use this instead of `Delete One`. [Reference](https://mongodb.github.io/node-mongodb-native/4.0/classes/collection.html#findoneanddelete) + #### Aggregate + Aggregation operations are expressions you can use to produce reduced and summarized results. [Reference](https://docs.mongodb.com/drivers/node/v4.0/fundamentals/aggregation/) + #### Delete One + Delete a record based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/deleteOne/) + #### Delete Many + Delete many records based on the filter. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/deleteMany/) + #### Bulk Operations + Perform bulk operations. [Reference](https://docs.mongodb.com/drivers/node/v4.0/usage-examples/bulkWrite/) ### Dynamic Quries + ```javascript { amount: { $lt: '{{ components.textinput1.value }}' }} -// Dates +// Dates // supported: Extended JSON syntax { createdAt: { $date: '{{ new Date('01/10/2020') }}'} } // not supported: MongoDB classic syntax { createdAt: new Date('01/10/2020') } ``` + Reference on [mongodb extended JSON](https://docs.mongodb.com/manual/reference/mongodb-extended-json/) supported data types diff --git a/docs/versioned_docs/version-2.19.0/data-sources/mssql.md b/docs/versioned_docs/version-2.19.0/data-sources/mssql.md index a8aa169ce5..0f9fa32b2f 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/mssql.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/mssql.md @@ -12,7 +12,7 @@ ToolJet can connect to MS SQL Server & Azure SQL databases to read and write dat Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. -To add new MS SQL Server / Azure SQL database, click on the '+' button on data sources panel at the left-bottom corner of the app editor. Select `SQL Server` from the modal that pops up. +To establish a connection with the MS SQL Server data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. ToolJet requires the following to connect to your PostgreSQL database. @@ -32,7 +32,7 @@ Click on 'Test connection' button to verify if the credentials are correct and t ## Querying SQL Server / Azure SQL databases Click on '+' button of the query manager at the bottom panel of the editor and select the database added in the previous step as the data source. -Click on the 'run' button to run the query. NOTE: Query should be saved before running. +Click on the 'run' button to run the query. ToolJet - Redis connection diff --git a/docs/versioned_docs/version-2.19.0/data-sources/mysql.md b/docs/versioned_docs/version-2.19.0/data-sources/mysql.md index 63af848971..c349d83171 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/mysql.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/mysql.md @@ -7,11 +7,11 @@ ToolJet can connect to MySQL databases to read and write data. ## Connection -To establish a connection with the MySQL datasource, you can either click on the `+Add New` button located on the query panel or navigate to the **[Global Datasources](/docs/data-sources/overview)** page through the ToolJet dashboard. +To establish a connection with the MySQL data source, you can either click on the `+Add New` button located on the query panel or navigate to the **[Data Sources](/docs/data-sources/overview)** page through the ToolJet dashboard.
-MySQL datasource +MySQL data source

@@ -20,31 +20,30 @@ To establish a connection with the MySQL datasource, you can either click on the Please make sure the **Host/IP** of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please **whitelist** our IP. ::: - **ToolJet requires the following to connect to your MySQL database:** -| Parameter | Description | -|:--- |:--- | -| Username | Username of the MySQL database | -| Password | Password of the MySQL database | -| Database name | Name of the MySQL database | +| Parameter | Description | +| :-------------- | :------------------------------------------------------------------------ | +| Username | Username of the MySQL database | +| Password | Password of the MySQL database | +| Database name | Name of the MySQL database | | Connection type | Connection type of the MySQL database: either **Hostname** or **Socket**. | If you are using **Hostname** as the connection type, you will need to provide the following information: -| Parameter | Description | -|:--- |:--- | -| Host/IP | Hostname or IP address of the MySQL database | -| Port | Port number of the MySQL database | -| SSL | Enable SSL connection to the MySQL database | +| Parameter | Description | +| :-------- | :------------------------------------------- | +| Host/IP | Hostname or IP address of the MySQL database | +| Port | Port number of the MySQL database | +| SSL | Enable SSL connection to the MySQL database | If you are using **Socket** as the connection type, you will need to provide the following information: -| Parameter | Description | -|:--- |:--- | +| Parameter | Description | +| :---------- | :---------------------- | | Socket path | Path of the socket file | -It is recommended to create a new MySQL database user so that you can control the access levels of ToolJet. +It is recommended to create a new MySQL database user so that you can control the access levels of ToolJet.
@@ -63,9 +62,10 @@ Once the MySQL data source is added, you can create queries to read and write da ### SQL mode -SQL mode can be used to query MySQL database using SQL queries. Select SQL mode from the dropdown and then enter the SQL query in the editor. +SQL mode can be used to query MySQL database using SQL queries. Select SQL mode from the dropdown and then enter the SQL query in the editor. **Example:** + ```sql SELECT * FROM users ``` @@ -80,7 +80,8 @@ SELECT * FROM users GUI mode can be used to query MySQL database without writing queries. Select GUI mode from the dropdown and then choose the operation **Bulk update using primary key**. Enter the **Table** name and **Primary key column** name. Now, in the editor enter the records in the form of an array of objects. Each object should contain the primary key column and the columns to be updated. -**Example:** +**Example:** + ```json {{ [ {id: 1, channel: 33}, {id:2, channel:24} ] }} ``` @@ -93,4 +94,4 @@ GUI mode can be used to query MySQL database without writing queries. Select GUI :::tip Query results can be transformed using transformations. Learn more about transformations [here](/docs/tutorial/transformations). -::: \ No newline at end of file +::: diff --git a/docs/versioned_docs/version-2.19.0/data-sources/notion.md b/docs/versioned_docs/version-2.19.0/data-sources/notion.md index 88000f3aa6..a224116642 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/notion.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/notion.md @@ -2,11 +2,15 @@ id: notion title: Notion --- + # Notion ToolJet can connect to a Notion workspace to do operations on notion pages, databases and blocks. + ## Connection +To establish a connection with the Notion data source, click on the `+Add new data source` button located on the query panel or navigate to the [Data Sources](https://docs.tooljet.com/docs/data-sources/overview) page from the ToolJet dashboard. + For integrating Notion with ToolJet we will need the API token. The API token can be generated from your Notion workspace settings. Read the official Notion docs for [Creating an internal integration with notion API](https://www.notion.so/help/create-integrations-with-the-notion-api).
@@ -18,37 +22,33 @@ For integrating Notion with ToolJet we will need the API token. The API token ca ## Querying Notion Notion API provides support for: + - **[Database](#database)** - **[Page](#page)** - **[Block](#blocks)** - **[User](#user)** - notion querying - :::tip Before querying Notion, you must share the database with your integration. Click the share button in your database view, find your integration name select it. - notion share - ::: ### Database On database resource you can perform the following operations: + - **[Retrieve a database](#1-retrieve-a-database)** - **[Query a database](#2-query-a-database)** - **[Create a database](#3-create-a-database)** - **[Update a database](#4-update-a-database)** - notion db - #### 1. Retrieve a database This operations retrieves a Database object using the ID specified. @@ -57,13 +57,12 @@ This operations retrieves a Database object using the ID specified. - **Database ID**: You'll find the Database ID in the url. Suppose this is the example url: `https://www.notion.so/workspace/XXX?v=YYY&p=ZZZ` then `XXX` is the database ID, `YYY` is the view ID and `ZZZ` is the page ID. - notion db retreieve - #### 2. Query a database This operation gets a list of **Pages** contained in the database, filtered and ordered according to the filter conditions and sort criteria provided in the query. + ##### Required parameters: - **Database ID** : You'll find the Database ID in the url. Suppose this is the example url: `https://www.notion.so/workspace/XXX?v=YYY&p=ZZZ` then `XXX` is the database ID, `YYY` is the view ID and `ZZZ` is the page ID. @@ -98,6 +97,7 @@ This operation creates a database as a subpage in the specified parent page, wit This operation updates an existing database as specified by the parameters. ##### Required parameters: + - **Database ID** ##### Optional parameters: @@ -112,24 +112,29 @@ This operation updates an existing database as specified by the parameters. ### Page On page resource you can perform the following operations: + - **[Retrieve a page](#1-retrieve-a-page)** - **[Create a page](#2-create-a-page)** - **[Update a page](#3-update-a-page)** - **[Retrieve a page property](#4-retrieve-a-page-property-item)** - **[Archive a page](#5-archive-delete-a-page)** - notion page - #### 1. Retrieve a page + This operation retrieves a **Page** object using the ID specified. + ##### Required parameters: + - **Page ID** #### 2. Create a page + This operation creates a new page in the specified database or as a child of an existing page. If the parent is a database, the property values of the new page in the properties parameter must conform to the parent database's property schema. If the parent is a page, the only valid property is title. + ##### Parameters: + - **Page ID** - **Properties** : Property values of this page - **Icon type** : Currently notion api accepts two icon options, emoji, external URL @@ -138,8 +143,11 @@ This operation creates a new page in the specified database or as a child of an - **Cover value** : Value of selected cover type #### 3. Update a page + This operation updates page property values for the specified page. Properties that are not set via the properties parameter will remain unchanged. + ##### Parameters: + - **Page ID** - **Parent type**: A database parent or page parent - **Properties** : Property values of this page @@ -150,77 +158,94 @@ This operation updates page property values for the specified page. Properties t - **Cover value** : Value of selected cover type #### 4. Retrieve a page property item + This operation retrieves a property_item object for a given page ID and property ID. Depending on the property type, the object returned will either be a value or a paginated list of property item values. See Property item objects for specifics. + ##### Parameters: + - **Page ID** - **Property ID** - **Limit** - **Start cursor** #### 5. Archive (delete) a page + ##### Required parameters: + - **Page ID** - **Archive**: Dropdown for archive and un archive the page ### Blocks + The following operations can be performed on the block resource: + - **[Retrieve a block](#1-retrieve-a-block)** - **[Append block children](#2-append-new-block-children)** - **[Retrieve block children](#3-retrieve-block-children)** - **[Update a block](#4-update-a-block)** - **[Delete a block](#5-delete-a-block)** - notion block - :::info To get the id for blocks, simply click on the menu icon for the block and click "Copy link". Afterwards, paste the link in the browser and it should look like this: `https://www.notion.so/Creating-Page-Sample-ee18b8779ae54f358b09221d6665ee15#7fcb3940a1264aadb2ad4ee9ffe11b0e` the string after **#** is the block id i.e. `7fcb3940a1264aadb2ad4ee9ffe11b0e`. ::: #### 1. Retrieve a block + This operation retrieves a **Block** object using the ID specified. ##### Required parameters: + - **Block ID** #### 2. Append new block children + This operation creates and appends new children blocks to the parent block_id specified. ##### Required parameters: + - **Block ID** - **Children**: Array of block objects #### 3. Retrieve block children + This operation retrieves a paginated array of child block objects contained in the block using the ID specified. ##### Required parameters: + - **Block ID** - **Limit** - **Start cursor** #### 4. Update a block + This operation updates the content for the specified block_id based on the block type. ##### Required parameters: + - **Block ID** - **Properties**: The block object type value with the properties to be updated - **Archive** #### 5. Delete a block + ##### Required parameters: + - **Block ID** ### User + The following operations can be performed on the user notion resource: #### 1. Retrieve a user from current workspace -This operation retrieves a User using the ID specified. +This operation retrieves a User using the ID specified. notion user ##### Required parameters: + - **User ID** #### 2. Retrieve list of users of a workspace @@ -228,6 +253,7 @@ This operation retrieves a User using the ID specified. This operation returns a paginated list of Users for the workspace. ##### Required parameters: + - **Limit** - **Start cursor** diff --git a/docs/versioned_docs/version-2.19.0/data-sources/s3.md b/docs/versioned_docs/version-2.19.0/data-sources/s3.md index ba1a029fb2..016630a1dd 100644 --- a/docs/versioned_docs/version-2.19.0/data-sources/s3.md +++ b/docs/versioned_docs/version-2.19.0/data-sources/s3.md @@ -9,7 +9,7 @@ ToolJet can connect to Amazon S3 buckets and perform various operation on them. ## Connection -To add a new S3 source, go to the **Datasources manager** on the left sidebar of the app editor and click on `Add datasource` button. Select **AWS S3** from the modal that pops up. +To add a new S3 source, go to the **Data sources manager** on the left sidebar of the app editor and click on `Add data source` button. Select **AWS S3** from the modal that pops up. ToolJet requires the following to connect to your AWS S3: @@ -133,5 +133,5 @@ The presigned URLs are useful if you want your user/customer to be able to uploa :::info -We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial **[here](https://blog.tooljet.com/building-an-app-to-view-and-upload-files-in-aws-s3-bucket/)**. +We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial **[here](https://blog.tooljet.com/build-an-aws-s3-broswer-with-tooljet/)**. ::: diff --git a/docs/versioned_docs/version-2.19.0/widgets/html.md b/docs/versioned_docs/version-2.19.0/widgets/html.md index 41908bef43..cc1e06c574 100644 --- a/docs/versioned_docs/version-2.19.0/widgets/html.md +++ b/docs/versioned_docs/version-2.19.0/widgets/html.md @@ -1,9 +1,9 @@ --- id: html -title: HTML +title: HTML Viewer --- -# HTML +# HTML Viewer HTML widget can be used to create your own HTML-CSS layout. diff --git a/docs/versioned_docs/version-2.19.0/widgets/rich-text-editor.md b/docs/versioned_docs/version-2.19.0/widgets/rich-text-editor.md index 7c5ba592d7..9d99fb99e3 100644 --- a/docs/versioned_docs/version-2.19.0/widgets/rich-text-editor.md +++ b/docs/versioned_docs/version-2.19.0/widgets/rich-text-editor.md @@ -1,8 +1,8 @@ --- id: rich-text-editor -title: Rich Text Editor +title: Text Editor --- -# Rich Text Editor +# Text Editor Rich Text Editor can be used to enter and edit the text in HTML format. It should be preferred for blog posts, forum posts or notes sections. The text is to be used as the label for the radio button. diff --git a/frontend/.version b/frontend/.version index 83ecbf1d7a..db65e2167e 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -2.20.2 +2.21.0 diff --git a/frontend/assets/translations/en.json b/frontend/assets/translations/en.json index 5feaac4eb5..ff40b511e6 100644 --- a/frontend/assets/translations/en.json +++ b/frontend/assets/translations/en.json @@ -737,19 +737,19 @@ }, "TextInput": { "displayName": "Text Input", - "description": "Text field for forms" + "description": "User text input field" }, "NumberInput": { "displayName": "Number Input", - "description": "Number field for forms" + "description": "Numeric input field" }, "PasswordInput": { "displayName": "Password Input", - "description": "Password input field for forms" + "description": "Secure text input" }, "Datepicker": { "displayName": "Date Picker", - "description": "Select a date and time" + "description": "Choose date and time" }, "Checkbox": { "displayName": "Checkbox", @@ -769,19 +769,19 @@ }, "DateRangePicker": { "displayName": "Range Picker", - "description": "Select a date range" + "description": "Choose date ranges" }, "Text": { "displayName": "Text", - "description": "Display markdown or HTML" + "description": "Display text or HTML" }, "Image": { "displayName": "Image", - "description": "Display an Image" + "description": "Show image files" }, "Container": { "displayName": "Container", - "description": "Wrapper for multiple components" + "description": "Group components" }, "Dropdown": { "displayName": "Dropdown", @@ -833,35 +833,35 @@ }, "Timer": { "displayName": "Timer", - "description": "timer" + "description": "Countdown or stopwatch" }, "Listview": { "displayName": "List View", - "description": "Wrapper for multiple components" + "description": "List multiple items" }, "Tags": { "displayName": "Tags", - "description": "Content can be shown as tags" + "description": "Display tag labels" }, "Pagination": { "displayName": "Pagination", - "description": "Pagination " + "description": "Navigate pages" }, "CircularProgressbar": { "displayName": "Circular Progressbar", - "description": "Show the progress using circular progressbar" + "description": "Show circular progress" }, "Spinner": { "displayName": "Spinner", - "description": "Spinner can be used to display loading status" + "description": "Indicate loading state" }, "Statistics": { "displayName": "Statistics", - "description": "Statistics can be used to display different statistical information" + "description": "Show key metrics" }, "RangeSlider": { "displayName": "Range Slider", - "description": "Can be used to show slider with a range" + "description": "Adjust value range" }, "Timeline": { "displayName": "Timeline", @@ -881,19 +881,19 @@ }, "CustomComponent": { "displayName": "Custom Component", - "description": "Add your custom react component" + "description": "Create React components" }, "ButtonGroup": { "displayName": "Button Group", - "description": "ButtonGroup" + "description": "Group of buttons" }, "PDF": { "displayName": "PDF", - "description": "Embed PDF file" + "description": "Embed PDF documents" }, "Steps": { "displayName": "Steps", - "description": "Steps" + "description": "Step-by-step navigation aid" }, "KanbanBoard": { "displayName": "Kanban Board", @@ -948,4 +948,4 @@ "tip": "Back to Home" } } -} \ No newline at end of file +} diff --git a/frontend/src/Editor/Header/EditAppName.jsx b/frontend/src/Editor/Header/EditAppName.jsx index 35400201e9..8ac727e5be 100644 --- a/frontend/src/Editor/Header/EditAppName.jsx +++ b/frontend/src/Editor/Header/EditAppName.jsx @@ -1,57 +1,136 @@ -import React from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import { ToolTip } from '@/_components'; import { appService } from '@/_services'; -import { handleHttpErrorMessages, validateName } from '../../_helpers/utils'; +import { handleHttpErrorMessages, validateAppName, validateName } from '@/_helpers/utils'; +import InfoOrErrorBox from './InfoOrErrorBox'; +import { toast } from 'react-hot-toast'; function EditAppName({ appId, appName = '', onNameChanged }) { const darkMode = localStorage.getItem('darkMode') === 'true'; - const [name, setName] = React.useState(appName); + const [name, setName] = useState(appName); + const [isValid, setIsValid] = useState(true); + const [isEditing, setIsEditing] = useState(false); + const [isError, setIsError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const [warningText, setWarningText] = useState(''); - React.useEffect(() => { + const inputRef = useRef(null); + + useEffect(() => { setName(appName); }, [appName]); - const saveAppName = async (name) => { - const newName = name.trim(); - if (!validateName(name, 'App name').status) { - return; - } - if (newName === appName) { - //will set back name without starting and ending spaces - setName(newName); - return; - } - await appService - .saveApp(appId, { name: newName }) - .then(() => { - onNameChanged(newName); - }) - .catch((error) => { - handleHttpErrorMessages(error, 'app'); - }); + const clearError = () => { + setIsError(false); + setErrorMessage(''); }; + const setError = (message) => { + setIsError(true); + setErrorMessage(message); + }; + + const saveAppName = async (newName) => { + const trimmedName = newName.trim(); + if (validateName(trimmedName, 'App name', true)?.errorMsg) { + setName(appName); + clearError(); + setIsEditing(false); + return; + } + + if (trimmedName === appName) { + setIsValid(true); + setIsEditing(false); + setName(appName); + return; + } + + try { + await appService.saveApp(appId, { name: trimmedName }); + onNameChanged(trimmedName); + setIsValid(true); + setIsEditing(false); + toast.success('App name successfully updated!'); + } catch (error) { + if (error.statusCode === 409) { + setError('App name already exists'); + } else { + clearError(); + setName(appName); + setIsEditing(false); + handleHttpErrorMessages(error, 'app'); + } + } + }; + + const handleBlur = () => { + saveAppName(name); + }; + + const handleFocus = () => { + setIsValid(true); + setIsEditing(true); + }; + + const handleInput = (e) => { + const newValue = e.target.value; + setName(newValue); + if (newValue.length >= 50) { + setWarningText('Maximum length has been reached'); + } else { + setWarningText(''); + clearError(); + } + }; + + const borderColor = isError + ? 'var(--light-tomato-10, #DB4324)' // Apply error border color + : darkMode + ? 'var(--dark-border-color, #2D3748)' // Change this to the appropriate dark border color + : 'var(--light-border-color, #FFF0EE)'; + return ( - -
+
+ { + onChange={() => { //this was quick fix. replace this with actual tooltip props and state later if (document.getElementsByClassName('tooltip').length) { document.getElementsByClassName('tooltip')[0].style.display = 'none'; } - validateName(e.target.value, 'App name', true); - setName(e.target.value); }} - onBlur={(e) => saveAppName(e.target.value)} - className="form-control-plaintext form-control-plaintext-sm" + onInput={handleInput} + onBlur={handleBlur} + onFocus={handleFocus} + onClick={() => { + inputRef.current.select(); + setIsEditing(true); + }} + className={`form-control-plaintext form-control-plaintext-sm ${ + (!isError && !isEditing) || isValid ? '' : 'is-invalid' + } ${isError ? 'error' : ''}`} // Add the 'error' class when there's an error + style={{ border: `1px solid ${borderColor}` }} value={name} maxLength={50} data-cy="app-name-input" /> -
- + + = 50 ? 'Maximum length has been reached' : 'App name should be unique and max 50 characters') + } + isWarning={warningText || name.length >= 50} + isError={isError} + darkMode={darkMode} + additionalClassName={isError ? 'error' : ''} + /> +
); } diff --git a/frontend/src/Editor/Header/InfoOrErrorBox.jsx b/frontend/src/Editor/Header/InfoOrErrorBox.jsx new file mode 100644 index 0000000000..755e20630a --- /dev/null +++ b/frontend/src/Editor/Header/InfoOrErrorBox.jsx @@ -0,0 +1,38 @@ +import React from 'react'; + +function InfoOrErrorBox({ active, message, isError, isWarning, darkMode, additionalClassName }) { + const color = isError ? 'var(--light-tomato-10, #DB4324)' : isWarning ? '#ED5F00' : 'var(--slate-light-10, #7E868C)'; + const boxStyle = { + display: active ? 'flex' : 'none', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'flex-start', + gap: '2px', + width: '200px', + height: '32px', + borderRadius: '6px', + border: `1px solid ${darkMode ? 'var(--dark-border-color, #2D3748)' : 'var(--light-border-color, #FFF0EE)'}`, + background: darkMode ? 'var(--dark-bg-01, #1E293B)' : 'var(--base-white-00, #FFF)', + boxShadow: '0px 1px 2px 0px rgba(16, 24, 40, 0.05)', + color: color, + zIndex: 10000, + position: 'absolute', + fontFamily: 'IBM Plex Sans', + fontSize: '10px', + fontStyle: 'normal', + fontWeight: 500, + lineHeight: '16px', + padding: '2px 8px', + ...(additionalClassName && { + ...additionalClassName.split(' ').reduce((acc, cls) => ({ ...acc, [cls]: true }), {}), + }), + }; + + return ( +
+ {message &&
{message}
} +
+ ); +} + +export default InfoOrErrorBox; diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index 2b598575f9..733cedc336 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -1052,7 +1052,7 @@ export const widgets = [ { name: 'TextInput', displayName: 'Text Input', - description: 'Text field for forms', + description: 'User text input field', component: 'TextInput', defaultSize: { width: 6, @@ -1183,7 +1183,7 @@ export const widgets = [ { name: 'NumberInput', displayName: 'Number Input', - description: 'Number field for forms', + description: 'Numeric input field', component: 'NumberInput', defaultSize: { width: 4, @@ -1309,7 +1309,7 @@ export const widgets = [ { name: 'PasswordInput', displayName: 'Password Input', - description: 'Password input field for forms', + description: 'Secure text input', component: 'PasswordInput', defaultSize: { width: 4, @@ -1396,7 +1396,7 @@ export const widgets = [ { name: 'Datepicker', displayName: 'Date Picker', - description: 'Select a date and time', + description: 'Choose date and time', component: 'Datepicker', defaultSize: { width: 5, @@ -1878,7 +1878,7 @@ export const widgets = [ { name: 'DateRangePicker', displayName: 'Range Picker', - description: 'Select a date range', + description: 'Choose date ranges', component: 'DaterangePicker', defaultSize: { width: 10, @@ -1976,7 +1976,7 @@ export const widgets = [ { name: 'Text', displayName: 'Text', - description: 'Display markdown or HTML', + description: 'Display text or HTML', component: 'Text', others: { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, @@ -2148,7 +2148,7 @@ export const widgets = [ { name: 'Image', displayName: 'Image', - description: 'Display an Image', + description: 'Show image files', defaultSize: { width: 3, height: 100, @@ -2282,7 +2282,7 @@ export const widgets = [ { name: 'Container', displayName: 'Container', - description: 'Wrapper for multiple components', + description: 'Group components', defaultSize: { width: 5, height: 200, @@ -3704,7 +3704,7 @@ export const widgets = [ { name: 'Timer', displayName: 'Timer', - description: 'timer', + description: 'Countdown or stopwatch', component: 'Timer', defaultSize: { width: 11, @@ -3797,7 +3797,7 @@ export const widgets = [ { name: 'Listview', displayName: 'List View', - description: 'Wrapper for multiple components', + description: 'List multiple items', defaultSize: { width: 20, height: 300, @@ -3983,7 +3983,7 @@ export const widgets = [ { name: 'Tags', displayName: 'Tags', - description: 'Content can be shown as tags', + description: 'Display tag labels', component: 'Tags', defaultSize: { width: 8, @@ -4039,7 +4039,7 @@ export const widgets = [ { name: 'Pagination', displayName: 'Pagination', - description: 'Pagination ', + description: 'Navigate pages', component: 'Pagination', defaultSize: { width: 10, @@ -4113,7 +4113,7 @@ export const widgets = [ { name: 'CircularProgressbar', displayName: 'Circular Progressbar', - description: 'Show the progress using circular progressbar', + description: 'Show circular progress', component: 'CircularProgressBar', defaultSize: { width: 7, @@ -4220,7 +4220,7 @@ export const widgets = [ { name: 'Spinner', displayName: 'Spinner', - description: 'Spinner can be used to display loading status', + description: 'Indicate loading state', component: 'Spinner', defaultSize: { width: 4, @@ -4277,7 +4277,7 @@ export const widgets = [ { name: 'Statistics', displayName: 'Statistics', - description: 'Statistics can be used to display different statistical information', + description: 'Show key metrics', component: 'Statistics', defaultSize: { width: 9.2, @@ -4367,7 +4367,7 @@ export const widgets = [ { name: 'RangeSlider', displayName: 'Range Slider', - description: 'Can be used to show slider with a range', + description: 'Adjust value range', component: 'RangeSlider', defaultSize: { width: 9, @@ -4679,7 +4679,7 @@ export const widgets = [ { name: 'CustomComponent', displayName: 'Custom Component', - description: 'Visual representation of a sequence of events', + description: 'Create React components', component: 'CustomComponent', properties: { data: { type: 'code', displayName: 'Data', validation: { schema: { type: 'object' } } }, @@ -4744,7 +4744,7 @@ ReactDOM.render(, document.body);`, { name: 'ButtonGroup', displayName: 'Button Group', - description: 'ButtonGroup', + description: 'Group of buttons', component: 'ButtonGroup', properties: { label: { @@ -4886,7 +4886,7 @@ ReactDOM.render(, document.body);`, { name: 'PDF', displayName: 'PDF', - description: 'Embed PDF file', + description: 'Embed PDF documents', component: 'PDF', properties: { url: { type: 'code', displayName: 'File URL', validation: { schema: { type: 'string' } } }, @@ -4945,7 +4945,7 @@ ReactDOM.render(, document.body);`, { name: 'Steps', displayName: 'Steps', - description: 'Steps', + description: 'Step-by-step navigation aid', component: 'Steps', properties: { steps: { diff --git a/frontend/src/HomePage/AppCard.jsx b/frontend/src/HomePage/AppCard.jsx index b0612991e5..79cd2b88e6 100644 --- a/frontend/src/HomePage/AppCard.jsx +++ b/frontend/src/HomePage/AppCard.jsx @@ -2,7 +2,7 @@ import React, { useState, useCallback, useEffect } from 'react'; import cx from 'classnames'; import { AppMenu } from './AppMenu'; import moment from 'moment'; -import { ToolTip } from '@/_components'; +import { ToolTip } from '@/_components/index'; import useHover from '@/_hooks/useHover'; import configs from './Configs/AppIcon.json'; import { Link, useNavigate } from 'react-router-dom'; @@ -66,18 +66,18 @@ export default function AppCard({ return (
-
+
-
+
{AppIcon && AppIcon}
- {(canCreateApp(app) || canDeleteApp(app)) && ( + {(canCreateApp(app) || canDeleteApp(app) || canUpdateApp(app)) && (
+ {canUpdateApp && ( + openAppActionModal('rename-app')} + /> + )} {canUpdateApp && ( openAppActionModal('remove-app-from-folder')} /> )} - + openAppActionModal('clone-app')} + /> )} diff --git a/frontend/src/HomePage/BlankPage.jsx b/frontend/src/HomePage/BlankPage.jsx index 433da87e99..e2407b31cc 100644 --- a/frontend/src/HomePage/BlankPage.jsx +++ b/frontend/src/HomePage/BlankPage.jsx @@ -1,23 +1,22 @@ import React, { useState } from 'react'; -import { toast } from 'react-hot-toast'; import TemplateLibraryModal from './TemplateLibraryModal/'; import { useTranslation } from 'react-i18next'; -import { libraryAppService } from '@/_services'; import EmptyIllustration from '@assets/images/no-apps.svg'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; -import { getWorkspaceId } from '../_helpers/utils'; import { useNavigate } from 'react-router-dom'; export const BlankPage = function BlankPage({ - createApp, - darkMode, - creatingApp, - handleImportApp, + readAndImport, isImportingApp, fileInput, + openCreateAppModal, + openCreateAppFromTemplateModal, + creatingApp, + darkMode, showTemplateLibraryModal, hideTemplateLibraryModal, viewTemplateLibraryModal, + canCreateApp, }) { const { t } = useTranslation(); const [deploying, setDeploying] = useState(false); @@ -29,25 +28,7 @@ export const BlankPage = function BlankPage({ { id: 'whatsapp-and-sms-crm', name: 'Whatsapp and sms crm' }, ]; - function deployApp(id) { - if (!deploying) { - const loadingToastId = toast.loading('Deploying app...'); - setDeploying(true); - libraryAppService - .deploy(id) - .then((data) => { - setDeploying(false); - toast.dismiss(loadingToastId); - toast.success('App created.'); - navigate(`/${getWorkspaceId()}/apps/${data.id}`); - }) - .catch((e) => { - toast.dismiss(loadingToastId); - toast.error(e.error); - setDeploying(false); - }); - } - } + const appCreationDisabled = !canCreateApp(); return (
@@ -70,27 +51,30 @@ export const BlankPage = function BlankPage({
Create new application
@@ -108,44 +93,54 @@ export const BlankPage = function BlankPage({
-
- Or choose from templates -
-
- {staticTemplates.map(({ id, name }) => { - return ( -
deployApp(id)}> -
+ {!appCreationDisabled && ( +
+
+ Or choose from templates +
+
+ {staticTemplates.map(({ id, name }) => { + return (
-
-

{ + openCreateAppFromTemplateModal({ id, name }); + }} + > +
- {name} -

+
+
+

+ {name} +

+
+
-
-
- ); - })} -
-
- -
+ ); + })} +
+
+ +
+
+ )}
@@ -155,6 +150,7 @@ export const BlankPage = function BlankPage({ onHide={hideTemplateLibraryModal} onCloseButtonClick={hideTemplateLibraryModal} darkMode={darkMode} + appCreationDisabled={appCreationDisabled} />
); diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index e56cb67396..84b8130b8a 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -1,7 +1,7 @@ import React from 'react'; import cx from 'classnames'; -import { appService, folderService, authenticationService } from '@/_services'; -import { ConfirmDialog } from '@/_components'; +import { appService, folderService, authenticationService, libraryAppService } from '@/_services'; +import { ConfirmDialog, AppModal } from '@/_components'; import Select from '@/_ui/Select'; import { Folders } from './Folders'; import { BlankPage } from './BlankPage'; @@ -61,6 +61,15 @@ class HomePageComponent extends React.Component { appOperations: {}, showTemplateLibraryModal: false, app: {}, + showCreateAppModal: false, + showCreateAppFromTemplateModal: false, + showImportAppModal: false, + showCloneAppModal: false, + showRenameAppModal: false, + fileContent: '', + fileName: '', + selectedTemplate: null, + deploying: false, }; } @@ -119,88 +128,140 @@ class HomePageComponent extends React.Component { this.fetchFolders(); }; - createApp = () => { + createApp = async (appName) => { let _self = this; _self.setState({ creatingApp: true }); - appService - .createApp({ icon: sample(iconList) }) - .then((data) => { - const workspaceId = getWorkspaceId(); - _self.props.navigate(`/${workspaceId}/apps/${data.id}`); - }) - .catch(({ error }) => { - toast.error(error); + try { + const data = await appService.createApp({ icon: sample(iconList), name: appName }); + + const workspaceId = getWorkspaceId(); + _self.props.navigate(`/${workspaceId}/apps/${data.id}`); + toast.success('App created successfully!'); + return true; + } catch (errorResponse) { + if (errorResponse.statusCode === 409) { _self.setState({ creatingApp: false }); - }); + return false; + } else { + throw errorResponse; + } + } + }; + + renameApp = async (newAppName, appId) => { + let _self = this; + _self.setState({ renamingApp: true }); + try { + await appService.saveApp(appId, { name: newAppName }); + await this.fetchApps(); + toast.success('App name has been updated!'); + return true; + } catch (errorResponse) { + if (errorResponse.statusCode === 409) { + console.log(errorResponse); + _self.setState({ renamingApp: false }); + return false; + } else { + throw errorResponse; + } + } }; deleteApp = (app) => { this.setState({ showAppDeletionConfirmation: true, appToBeDeleted: app }); }; - cloneApp = (app) => { + cloneApp = async (appId, appName) => { this.setState({ isCloningApp: true }); - appService - .cloneResource({ app: [{ id: app.id }], organization_id: getWorkspaceId() }) - .then((data) => { - toast.success('App cloned successfully.'); - this.setState({ isCloningApp: false }); - this.props.navigate(`/${getWorkspaceId()}/apps/${data.imports.app[0].id}`); - }) - .catch(({ _error }) => { - toast.error('Could not clone the app.'); - this.setState({ isCloningApp: false }); - console.log(_error); - }); + try { + const data = await appService.cloneApp(appName, appId); + toast.success('App cloned successfully!'); + this.setState({ isCloningApp: false }); + this.props.navigate(`/${getWorkspaceId()}/apps/${data.id}`); + return true; + } catch (_error) { + this.setState({ isCloningApp: false }); + if (_error.statusCode === 409) { + return false; + } else { + throw _error; + } + } }; exportApp = async (app) => { this.setState({ isExportingApp: true, app: app }); }; - handleImportApp = (event) => { - const fileReader = new FileReader(); - fileReader.readAsText(event.target.files[0], 'UTF-8'); - fileReader.onload = (event) => { - const fileContent = event.target.result; - this.setState({ isImportingApp: true }); - try { - const organization_id = getWorkspaceId(); - let importJSON = JSON.parse(fileContent); - // For backward compatibility with legacy app import - const isLegacyImport = isEmpty(importJSON.tooljet_version); - if (isLegacyImport) { - importJSON = { app: [{ definition: importJSON }], tooljet_version: importJSON.tooljetVersion }; - } - const requestBody = { organization_id, ...importJSON }; - appService - .importResource(requestBody) - .then((data) => { - toast.success('Imported successfully.'); - this.setState({ - isImportingApp: false, - }); - if (!isEmpty(data.imports.app)) { - this.props.navigate(`/${getWorkspaceId()}/apps/${data.imports.app[0].id}`); - } else if (!isEmpty(data.imports.tooljet_database)) { - this.props.navigate(`/${getWorkspaceId()}/database`); - } - }) - .catch(({ error }) => { - toast.error(`Could not import: ${error}`); - this.setState({ - isImportingApp: false, - }); - }); - } catch (error) { - toast.error(`Could not import: ${error}`); - this.setState({ - isImportingApp: false, - }); - } - // set file input as null to handle same file upload + readAndImport = (event) => { + try { + const file = event.target.files[0]; + if (!file) return; + + const fileReader = new FileReader(); + const fileName = file.name.replace('.json', '').substring(0, 50); + fileReader.readAsText(file, 'UTF-8'); + fileReader.onload = (event) => { + const result = event.target.result; + const fileContent = JSON.parse(result); + this.setState({ fileContent, fileName, showImportAppModal: true }); + }; + fileReader.onerror = (error) => { + throw new Error(`Could not import the app: ${error}`); + }; event.target.value = null; - }; + } catch (error) { + toast.error(error.message); + } + }; + + importFile = async (importJSON, appName) => { + this.setState({ isImportingApp: true }); + // For backward compatibility with legacy app import + const organization_id = getWorkspaceId(); + const isLegacyImport = isEmpty(importJSON.tooljet_version); + if (isLegacyImport) { + importJSON = { app: [{ definition: importJSON }], tooljet_version: importJSON.tooljetVersion }; + } + const requestBody = { organization_id, appName, ...importJSON }; + try { + const data = await appService.importResource(requestBody); + toast.success('App imported successfully.'); + this.setState({ + isImportingApp: false, + }); + if (!isEmpty(data.imports.app)) { + this.props.navigate(`/${getWorkspaceId()}/apps/${data.imports.app[0].id}`); + } else if (!isEmpty(data.imports.tooljet_database)) { + this.props.navigate(`/${getWorkspaceId()}/database`); + } + } catch (error) { + this.setState({ + isImportingApp: false, + }); + if (error.statusCode === 409) { + return false; + } + } + }; + + deployApp = async (event, appName, selectedApp) => { + event.preventDefault(); + const id = selectedApp.id; + this.setState({ deploying: true }); + try { + const data = await libraryAppService.deploy(id, appName); + this.setState({ deploying: false }); + toast.success('App created successfully!', { position: 'top-center' }); + this.props.navigate(`/${getWorkspaceId()}/apps/${data.app[0].id}`); + } catch (e) { + this.setState({ deploying: false }); + if (e.statusCode === 409) { + return false; + } else { + return e; + } + } }; canUserPerform(user, action, app) { @@ -387,6 +448,18 @@ class HomePageComponent extends React.Component { showRemoveAppFromFolderConfirmation: true, }); break; + case 'clone-app': + this.setState({ + appOperations: { ...appOperations, selectedApp: app, selectedIcon: app?.icon }, + showCloneAppModal: true, + }); + break; + case 'rename-app': + this.setState({ + appOperations: { ...appOperations, selectedApp: app }, + showRenameAppModal: true, + }); + break; } }; @@ -442,6 +515,22 @@ class HomePageComponent extends React.Component { this.setState({ showTemplateLibraryModal: false }); }; + openCreateAppFromTemplateModal = (template) => { + this.setState({ showCreateAppFromTemplateModal: true, selectedTemplate: template }); + }; + + closeCreateAppFromTemplateModal = () => { + this.setState({ showCreateAppFromTemplateModal: false, selectedTemplate: null }); + }; + + openCreateAppModal = () => { + this.setState({ showCreateAppModal: true }); + }; + + closeCreateAppModal = () => { + this.setState({ showCreateAppModal: false }); + }; + render() { const { apps, @@ -457,14 +546,78 @@ class HomePageComponent extends React.Component { appSearchKey, showAddToFolderModal, showChangeIconModal, + showCloneAppModal, appOperations, isExportingApp, appToBeDeleted, app, + showCreateAppModal, + showImportAppModal, + fileContent, + fileName, + showRenameAppModal, + showCreateAppFromTemplateModal, } = this.state; return (
+ {showCreateAppModal && ( + + )} + {showCloneAppModal && ( + this.setState({ showCloneAppModal: false })} + processApp={this.cloneApp} + show={() => this.setState({ showCloneAppModal: true })} + selectedAppId={appOperations?.selectedApp?.id} + selectedAppName={appOperations?.selectedApp?.name} + title={'Clone app'} + actionButton={'Clone app'} + actionLoadingButton={'Cloning'} + /> + )} + {showImportAppModal && ( + this.setState({ showImportAppModal: false })} + processApp={this.importFile} + fileContent={fileContent} + show={() => this.setState({ showImportAppModal: true })} + selectedAppName={fileName} + title={'Import app'} + actionButton={'Import app'} + actionLoadingButton={'Importing'} + /> + )} + {showCreateAppFromTemplateModal && ( + + )} + {showRenameAppModal && ( + this.setState({ showRenameAppModal: true })} + closeModal={() => this.setState({ showRenameAppModal: false })} + processApp={this.renameApp} + selectedAppId={appOperations.selectedApp.id} + selectedAppName={appOperations.selectedApp.name} + title={'Rename app'} + actionButton={'Rename app'} + actionLoadingButton={'Renaming'} + /> + )} this.cancelDeleteAppDialog()} darkMode={this.props.darkMode} /> -
diff --git a/frontend/src/HomePage/Modal.jsx b/frontend/src/HomePage/Modal.jsx index e752886a79..ce33b08d45 100644 --- a/frontend/src/HomePage/Modal.jsx +++ b/frontend/src/HomePage/Modal.jsx @@ -1,8 +1,13 @@ import React from 'react'; import { default as BootstrapModal } from 'react-bootstrap/Modal'; -export default function Modal({ title, show, closeModal, customClassName, children }) { +export default function Modal({ title, show, closeModal, customClassName, children, footerContent = null }) { const darkMode = localStorage.getItem('darkMode') === 'true'; + const modalFooter = footerContent ? ( + + {footerContent} + + ) : null; return ( closeModal(false)} @@ -31,6 +36,7 @@ export default function Modal({ title, show, closeModal, customClassName, childr > {children} + {modalFooter ? modalFooter : <>} ); } diff --git a/frontend/src/HomePage/TemplateLibraryModal/TemplateLibraryModal.jsx b/frontend/src/HomePage/TemplateLibraryModal/TemplateLibraryModal.jsx index 7ada977916..0773a98fc2 100644 --- a/frontend/src/HomePage/TemplateLibraryModal/TemplateLibraryModal.jsx +++ b/frontend/src/HomePage/TemplateLibraryModal/TemplateLibraryModal.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Modal, Container, Row, Col } from 'react-bootstrap'; import Categories from './Categories'; import AppList from './AppList'; @@ -25,6 +25,7 @@ export default function TemplateLibraryModal(props) { (app) => selectedCategory.id === 'all' || app.category === selectedCategory.id ); const [selectedApp, selectApp] = useState(undefined); + const [showCreateAppFromTemplateModal, setShowCreateAppFromTemplateModal] = useState(false); const { t } = useTranslation(); useEffect(() => { @@ -51,31 +52,10 @@ export default function TemplateLibraryModal(props) { const [deploying, setDeploying] = useState(false); - function deployApp(event) { - event.preventDefault(); - const id = selectedApp.id; - setDeploying(true); - libraryAppService - .deploy(id) - .then((data) => { - setDeploying(false); - props.onCloseButtonClick(); - toast.success('App created.', { - position: 'top-center', - }); - navigate(`/${getWorkspaceId()}/apps/${data.app[0].id}`); - }) - .catch((e) => { - toast.error(e.error, { - position: 'top-center', - }); - setDeploying(false); - }); - } - return ( { - deployApp(e); + onClick={() => { + props.openCreateAppFromTemplateModal(selectedApp); + setShowCreateAppFromTemplateModal(false); + props.onCloseButtonClick(); }} isLoading={deploying} - className=" ms-2 " + className="ms-2" + disabled={props.appCreationDisabled} > {t('homePage.templateLibraryModal.createAppfromTemplate', 'Create application from template')} diff --git a/frontend/src/_components/AppModal.jsx b/frontend/src/_components/AppModal.jsx new file mode 100644 index 0000000000..6ff81c6b79 --- /dev/null +++ b/frontend/src/_components/AppModal.jsx @@ -0,0 +1,187 @@ +import React, { useState, useEffect, useContext, useRef } from 'react'; +import { toast } from 'react-hot-toast'; +import Modal from '../HomePage/Modal'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import _ from 'lodash'; +import { validateAppName } from '@/_helpers/utils'; + +export function AppModal({ + closeModal, + processApp, + show, + fileContent = null, + templateDetails = null, + selectedAppId = null, + selectedAppName = null, + title, + actionButton, + actionLoadingButton, +}) { + if (!selectedAppName && templateDetails) { + selectedAppName = templateDetails?.name || ''; + } else if (!selectedAppName) { + selectedAppName = ''; + } + + if (actionButton === 'Clone app') { + if (selectedAppName.length >= 45) { + selectedAppName = selectedAppName.slice(0, 45) + '_Copy'; + } else { + selectedAppName = selectedAppName + '_Copy'; + } + } + + const [deploying, setDeploying] = useState(false); + const [newAppName, setNewAppName] = useState(selectedAppName); + const [errorText, setErrorText] = useState(''); + const [infoText, setInfoText] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isNameChanged, setIsNameChanged] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const [clearInput, setClearInput] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + setIsNameChanged(newAppName?.trim() !== selectedAppName); + }, [newAppName, selectedAppName]); + + useEffect(() => { + setIsSuccess(false); + }, [show]); + + useEffect(() => { + inputRef.current?.select(); + }, [show]); + + useEffect(() => { + setIsSuccess(false); + setClearInput(false); + setNewAppName(selectedAppName); + }, [selectedAppName]); + + const handleAction = async (e) => { + setDeploying(true); + const trimmedAppName = newAppName.trim(); + setNewAppName(trimmedAppName); + if (!errorText) { + setIsLoading(true); + try { + let success = true; + //create app from template + if (templateDetails) { + success = await processApp(e, trimmedAppName, templateDetails); + //import app + } else if (fileContent) { + success = await processApp(fileContent, trimmedAppName); + //rename app/clone existing app + } else if (selectedAppId) { + success = await processApp(trimmedAppName, selectedAppId); + //create app from scratch + } else { + success = await processApp(trimmedAppName); + } + if (success === false) { + setErrorText('App name already exists'); + setInfoText(''); + } else { + setErrorText(''); + setInfoText(''); + closeModal(); + } + } catch (error) { + toast.error(e.error, { + position: 'top-center', + }); + } + } + setIsLoading(false); + }; + + const handleInputChange = (e) => { + const newAppName = e.target.value; + const trimmedName = newAppName.trim(); + setNewAppName(newAppName); + if (newAppName.length >= 50) { + setInfoText('Maximum length has been reached'); + } else { + setInfoText(''); + const error = validateAppName(trimmedName); + setErrorText(error?.errorMsg || ''); + } + }; + + const createBtnDisableState = + isLoading || + errorText || + (actionButton === 'Rename app' && (!isNameChanged || newAppName.trim().length === 0 || newAppName.length > 50)) || // For rename case + (actionButton !== 'Rename app' && (newAppName.length > 50 || newAppName.trim().length === 0)); + + return ( + + + Cancel + + handleAction(e)} data-cy={actionButton} disabled={createBtnDisableState}> + {isLoading ? actionLoadingButton : actionButton} + + + } + > +
+
+ + + {errorText ? ( + + {errorText} + + ) : infoText || newAppName.length >= 50 ? ( + + {infoText || 'Maximum length has been reached'} + + ) : ( + + App name must be unique and max 50 characters + + )} +
+
+
+ ); +} diff --git a/frontend/src/_components/index.js b/frontend/src/_components/index.js index 9eee8a94bb..cc691843ba 100644 --- a/frontend/src/_components/index.js +++ b/frontend/src/_components/index.js @@ -4,6 +4,7 @@ export * from './ConfirmDialog'; export * from './DarkModeToggle'; export * from './SearchBox'; export * from './ToolTip'; +export * from './AppModal'; export * from './ImageWithSpinner'; export * from './Menu'; export * from './LoginLoader'; diff --git a/frontend/src/_helpers/utils.js b/frontend/src/_helpers/utils.js index 0233d1e96f..f3fb99001d 100644 --- a/frontend/src/_helpers/utils.js +++ b/frontend/src/_helpers/utils.js @@ -920,6 +920,22 @@ export function isExpectedDataType(data, expectedDataType) { return data; } +export const validateAppName = (name, showError = false) => { + const newName = name.trim(); + let errorMsg = ''; + if (newName.length > 50) { + errorMsg = `Maximum length has been reached`; + showError && + toast.error(errorMsg, { + id: '1', + }); + } + return { + status: !(errorMsg.length > 0), + errorMsg, + }; +}; + export const validateName = (name, nameType, showError = false, allowSpecialChars = true) => { const newName = name.trim(); let errorMsg = ''; diff --git a/frontend/src/_services/app.service.js b/frontend/src/_services/app.service.js index 0329954eac..a937abba5c 100644 --- a/frontend/src/_services/app.service.js +++ b/frontend/src/_services/app.service.js @@ -48,8 +48,13 @@ function createApp(body = {}) { return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse); } -function cloneApp(id) { - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include' }; +function cloneApp(id, name) { + const requestOptions = { + method: 'POST', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify({ name }), + }; return fetch(`${config.apiUrl}/apps/${id}/clone`, requestOptions).then(handleResponse); } @@ -97,8 +102,13 @@ function getVersions(id) { return fetch(`${config.apiUrl}/apps/${id}/versions`, requestOptions).then(handleResponse); } -function importApp(body) { - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; +function importApp(app, name) { + const requestOptions = { + method: 'POST', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify({ app, name }), + }; return fetch(`${config.apiUrl}/apps/import`, requestOptions).then(handleResponse); } diff --git a/frontend/src/_services/library-app.service.js b/frontend/src/_services/library-app.service.js index 799e9cca92..4f60ae4b3a 100644 --- a/frontend/src/_services/library-app.service.js +++ b/frontend/src/_services/library-app.service.js @@ -6,9 +6,10 @@ export const libraryAppService = { templateManifests, }; -function deploy(identifier) { +function deploy(identifier, appName) { const body = { identifier, + appName, }; const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index ab62b43304..96e8ce13d2 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -10226,6 +10226,9 @@ tbody { border: 1px solid var(--indigo9) !important; box-shadow: none !important; } + &.input-error-border { + border-color: #DB4324 !important; + } &:-webkit-autofill { box-shadow: 0 0 0 1000px var(--base) inset !important; @@ -11298,6 +11301,16 @@ tbody { background-color: #F1F3F5; color: #C1C8CD; } +} + +.modal-divider { + border-top: 1px solid #dee2e6; + padding: 10px; +} + +.dark-theme-modal-divider { + border-top: 1px solid var(--slate5) !important; + padding: 10px; .nav-item { background-color: transparent !important; diff --git a/server/.version b/server/.version index 83ecbf1d7a..db65e2167e 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -2.20.2 +2.21.0 diff --git a/server/src/controllers/app_import_export.controller.ts b/server/src/controllers/app_import_export.controller.ts index 5269aa092a..4865706868 100644 --- a/server/src/controllers/app_import_export.controller.ts +++ b/server/src/controllers/app_import_export.controller.ts @@ -6,6 +6,7 @@ import { AppsAbilityFactory } from 'src/modules/casl/abilities/apps-ability.fact import { App } from 'src/entities/app.entity'; import { AppImportExportService } from '@services/app_import_export.service'; import { User } from 'src/decorators/user.decorator'; +import { AppImportDto } from '@dto/app-import.dto'; @Controller('apps') export class AppsImportExportController { @@ -17,13 +18,14 @@ export class AppsImportExportController { @UseGuards(JwtAuthGuard) @Post('/import') - async import(@User() user, @Body() body) { + async import(@User() user, @Body() appImportDto: AppImportDto) { const ability = await this.appsAbilityFactory.appsActions(user); if (!ability.can('createApp', App)) { throw new ForbiddenException('You do not have permissions to perform this action'); } - const app = await this.appImportExportService.import(user, body); + const { name: appName, app: appContent } = appImportDto; + const app = await this.appImportExportService.import(user, appContent, appName); return decamelizeKeys(app); } diff --git a/server/src/controllers/apps.controller.ts b/server/src/controllers/apps.controller.ts index 45873792d0..7564af0850 100644 --- a/server/src/controllers/apps.controller.ts +++ b/server/src/controllers/apps.controller.ts @@ -21,12 +21,14 @@ import { FoldersService } from '@services/folders.service'; import { App } from 'src/entities/app.entity'; import { User } from 'src/decorators/user.decorator'; import { AppUpdateDto } from '@dto/app-update.dto'; +import { AppCreateDto } from '@dto/app-create.dto'; import { VersionCreateDto } from '@dto/version-create.dto'; import { VersionEditDto } from '@dto/version-edit.dto'; import { dbTransactionWrap } from 'src/helpers/utils.helper'; import { EntityManager } from 'typeorm'; import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor'; import { AppDecorator } from 'src/decorators/app.decorator'; +import { AppCloneDto } from '@dto/app-clone.dto'; @Controller('apps') export class AppsController { @@ -38,17 +40,20 @@ export class AppsController { @UseGuards(JwtAuthGuard) @Post() - async create(@User() user, @Body('icon') icon: string) { + async create(@User() user, @Body() appCreateDto: AppCreateDto) { const ability = await this.appsAbilityFactory.appsActions(user); + const name = appCreateDto.name; + const icon = appCreateDto.icon; if (!ability.can('createApp', App)) { throw new ForbiddenException('You do not have permissions to perform this action'); } return await dbTransactionWrap(async (manager: EntityManager) => { - const app = await this.appsService.create(user, manager); + const app = await this.appsService.create(name, user, manager); const appUpdateDto = new AppUpdateDto(); + appUpdateDto.name = name; appUpdateDto.slug = app.id; appUpdateDto.icon = icon; await this.appsService.update(app.id, appUpdateDto, manager); @@ -154,14 +159,14 @@ export class AppsController { @UseGuards(JwtAuthGuard) @UseInterceptors(ValidAppInterceptor) @Post(':id/clone') - async clone(@User() user, @AppDecorator() app: App) { + async clone(@User() user, @AppDecorator() app: App, @Body() appCloneDto: AppCloneDto) { const ability = await this.appsAbilityFactory.appsActions(user, app.id); if (!ability.can('cloneApp', app)) { throw new ForbiddenException('You do not have permissions to perform this action'); } - - const result = await this.appsService.clone(app, user); + const appName = appCloneDto.name; + const result = await this.appsService.clone(app, user, appName); const response = decamelizeKeys(result); return response; diff --git a/server/src/controllers/library_apps.controller.ts b/server/src/controllers/library_apps.controller.ts index 67c96f83d8..78a058ceef 100644 --- a/server/src/controllers/library_apps.controller.ts +++ b/server/src/controllers/library_apps.controller.ts @@ -15,14 +15,15 @@ export class LibraryAppsController { @Post() @UseGuards(JwtAuthGuard) - async create(@User() user, @Body('identifier') identifier) { + async create(@User() user, @Body('identifier') identifier, @Body('appName') appName) { const ability = await this.appsAbilityFactory.appsActions(user); if (!ability.can('createApp', App)) { throw new ForbiddenException('You do not have permissions to perform this action'); } - const result = await this.libraryAppCreationService.perform(user, identifier); - return result; + const newApp = await this.libraryAppCreationService.perform(user, identifier, appName); + + return newApp; } @Get() diff --git a/server/src/dto/app-clone.dto.ts b/server/src/dto/app-clone.dto.ts new file mode 100644 index 0000000000..139883d736 --- /dev/null +++ b/server/src/dto/app-clone.dto.ts @@ -0,0 +1,8 @@ +import { IsString, IsNotEmpty, MaxLength } from 'class-validator'; + +export class AppCloneDto { + @IsNotEmpty() + @IsString() + @MaxLength(50, { message: 'Maximum length has been reached.' }) + name: string; +} diff --git a/server/src/dto/app-create.dto.ts b/server/src/dto/app-create.dto.ts new file mode 100644 index 0000000000..e2b959838b --- /dev/null +++ b/server/src/dto/app-create.dto.ts @@ -0,0 +1,12 @@ +import { IsString, IsOptional, IsNotEmpty, MaxLength } from 'class-validator'; + +export class AppCreateDto { + @IsNotEmpty() + @IsString() + @MaxLength(50, { message: 'Maximum length has been reached.' }) + name: string; + + @IsOptional() + @IsString() + icon?: string; +} diff --git a/server/src/dto/app-import.dto.ts b/server/src/dto/app-import.dto.ts new file mode 100644 index 0000000000..85863c08e2 --- /dev/null +++ b/server/src/dto/app-import.dto.ts @@ -0,0 +1,11 @@ +import { IsString, IsNotEmpty, MaxLength } from 'class-validator'; + +export class AppImportDto { + @IsNotEmpty() + @IsString() + @MaxLength(50, { message: 'Maximum length has been reached.' }) + name: string; + + @IsNotEmpty() + app: object; +} diff --git a/server/src/dto/import-resources.dto.ts b/server/src/dto/import-resources.dto.ts index 50cefcfb21..9d6257d4af 100644 --- a/server/src/dto/import-resources.dto.ts +++ b/server/src/dto/import-resources.dto.ts @@ -10,6 +10,9 @@ export class ImportResourcesDto { @IsOptional() app: ImportAppDto[]; + @IsOptional() + appName: string; + @IsOptional() tooljet_database: ImportTooljetDatabaseDto[]; } diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index f9e2f611c4..3d3066ba15 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -11,11 +11,12 @@ import { GroupPermission } from 'src/entities/group_permission.entity'; import { User } from 'src/entities/user.entity'; import { EntityManager } from 'typeorm'; import { DataSourcesService } from './data_sources.service'; -import { dbTransactionWrap, defaultAppEnvironments, truncateAndReplace } from 'src/helpers/utils.helper'; +import { dbTransactionWrap, defaultAppEnvironments, catchDbException } from 'src/helpers/utils.helper'; import { AppEnvironmentService } from './app_environments.service'; import { convertAppDefinitionFromSinglePageToMultiPage } from '../../lib/single-page-to-and-from-multipage-definition-conversion'; import { DataSourceScopes, DataSourceTypes } from 'src/helpers/data_source.constants'; import { Organization } from 'src/entities/organization.entity'; +import { DataBaseConstraints } from 'src/helpers/db_constraints.constants'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { Plugin } from 'src/entities/plugin.entity'; @@ -151,7 +152,7 @@ export class AppImportExportService { }); } - async import(user: User, appParamsObj: any, externalResourceMappings = {}): Promise { + async import(user: User, appParamsObj: any, appName: string, externalResourceMappings = {}): Promise { if (typeof appParamsObj !== 'object') { throw new BadRequestException('Invalid params for app import'); } @@ -171,6 +172,7 @@ export class AppImportExportService { const schemaUnifiedAppParams = appParams?.schemaDetails?.multiPages ? appParams : convertSinglePageSchemaToMultiPageSchema(appParams); + schemaUnifiedAppParams.name = appName; await dbTransactionWrap(async (manager) => { importedApp = await this.createImportedAppForUser(manager, schemaUnifiedAppParams, user); @@ -194,18 +196,24 @@ export class AppImportExportService { } async createImportedAppForUser(manager: EntityManager, appParams: any, user: User): Promise { - const importedApp = manager.create(App, { - name: truncateAndReplace(appParams.name), - organizationId: user.organizationId, - userId: user.id, - slug: null, // Prevent db unique constraint error. - icon: appParams.icon, - isPublic: false, - createdAt: new Date(), - updatedAt: new Date(), - }); - await manager.save(importedApp); - return importedApp; + return await catchDbException( + async () => { + const importedApp = manager.create(App, { + name: appParams.name, + organizationId: user.organizationId, + userId: user.id, + slug: null, + icon: appParams.icon, + isPublic: false, + createdAt: new Date(), + updatedAt: new Date(), + }); + await manager.save(importedApp); + return importedApp; + }, + DataBaseConstraints.APP_NAME_UNIQUE, + 'This app name is already taken.' + ); } extractImportDataFromAppParams(appParams: Record): { diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index a55a90cd72..8996fb368d 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -12,13 +12,7 @@ import { AppGroupPermission } from 'src/entities/app_group_permission.entity'; import { AppImportExportService } from './app_import_export.service'; import { DataSourcesService } from './data_sources.service'; import { Credential } from 'src/entities/credential.entity'; -import { - catchDbException, - cleanObject, - dbTransactionWrap, - defaultAppEnvironments, - generateNextName, -} from 'src/helpers/utils.helper'; +import { catchDbException, cleanObject, dbTransactionWrap, defaultAppEnvironments } from 'src/helpers/utils.helper'; import { AppUpdateDto } from '@dto/app-update.dto'; import { viewableAppsQuery } from 'src/helpers/queries'; import { VersionEditDto } from '@dto/version-edit.dto'; @@ -103,35 +97,40 @@ export class AppsService { }); } - async create(user: User, manager: EntityManager): Promise { + async create(name: string, user: User, manager: EntityManager): Promise { return await dbTransactionWrap(async (manager: EntityManager) => { - const name = await generateNextName('My app'); - const app = await manager.save( - manager.create(App, { - name, - createdAt: new Date(), - updatedAt: new Date(), - organizationId: user.organizationId, - userId: user.id, - }) + return await catchDbException( + async () => { + const app = await manager.save( + manager.create(App, { + name, + createdAt: new Date(), + updatedAt: new Date(), + organizationId: user.organizationId, + userId: user.id, + }) + ); + + //create default app version + await this.createVersion(user, app, 'v1', null, null, manager); + + await manager.save( + manager.create(AppUser, { + userId: user.id, + appId: app.id, + role: 'admin', + createdAt: new Date(), + updatedAt: new Date(), + }) + ); + + await this.createAppGroupPermissionsForAdmin(app, manager); + return app; + }, + DataBaseConstraints.APP_NAME_UNIQUE, + 'This app name is already taken.' ); - - //create default app version - await this.createVersion(user, app, 'v1', null, null, manager); - - await manager.save( - manager.create(AppUser, { - userId: user.id, - appId: app.id, - role: 'admin', - createdAt: new Date(), - updatedAt: new Date(), - }) - ); - - await this.createAppGroupPermissionsForAdmin(app, manager); - return app; - }, manager); + }); } async createAppGroupPermissionsForAdmin(app: App, manager: EntityManager): Promise { @@ -170,9 +169,9 @@ export class AppsService { } } - async clone(existingApp: App, user: User): Promise { + async clone(existingApp: App, user: User, appName: string): Promise { const appWithRelations = await this.appImportExportService.export(user, existingApp.id); - const clonedApp = await this.appImportExportService.import(user, appWithRelations); + const clonedApp = await this.appImportExportService.import(user, appWithRelations, appName); return clonedApp; } diff --git a/server/src/services/import_export_resources.service.ts b/server/src/services/import_export_resources.service.ts index 2ca413c15e..fce758c0cf 100644 --- a/server/src/services/import_export_resources.service.ts +++ b/server/src/services/import_export_resources.service.ts @@ -59,9 +59,10 @@ export class ImportExportResourcesService { } if (importResourcesDto.app) { + const appName = importResourcesDto.appName; for (const appImportDto of importResourcesDto.app) { user.organizationId = importResourcesDto.organization_id; - const createdApp = await this.appImportExportService.import(user, appImportDto.definition, { + const createdApp = await this.appImportExportService.import(user, appImportDto.definition, appName, { tooljet_database: tableNameMapping, }); imports.app.push({ id: createdApp.id, name: createdApp.name }); diff --git a/server/src/services/library_app_creation.service.ts b/server/src/services/library_app_creation.service.ts index 49a231418b..1830c12883 100644 --- a/server/src/services/library_app_creation.service.ts +++ b/server/src/services/library_app_creation.service.ts @@ -14,7 +14,7 @@ export class LibraryAppCreationService { private readonly logger: Logger ) {} - async perform(currentUser: User, identifier: string) { + async perform(currentUser: User, identifier: string, appName: string) { const templateDefinition = this.findTemplateDefinition(identifier); const importDto = new ImportResourcesDto(); importDto.organization_id = currentUser.organizationId; @@ -24,7 +24,7 @@ export class LibraryAppCreationService { if (this.isVersionGreaterThanOrEqual(templateDefinition.tooljet_version, '2.16.0')) { return await this.importExportResourcesService.import(currentUser, importDto); } else { - const importedApp = await this.appImportExportService.import(currentUser, templateDefinition); + const importedApp = await this.appImportExportService.import(currentUser, templateDefinition, appName); return { app: [importedApp], tooljet_database: [], diff --git a/server/test/controllers/apps.e2e-spec.ts b/server/test/controllers/apps.e2e-spec.ts index 754d54d93a..278654f13d 100644 --- a/server/test/controllers/apps.e2e-spec.ts +++ b/server/test/controllers/apps.e2e-spec.ts @@ -82,11 +82,15 @@ describe('apps controller', () => { }); await createApplicationVersion(app, application); + const appName = 'My app'; for (const userData of [viewerUserData, developerUserData]) { const response = await request(app.getHttpServer()) .post(`/api/apps`) .set('tj-workspace-id', userData.user.defaultOrganizationId) - .set('Cookie', userData['tokenCookie']); + .set('Cookie', userData['tokenCookie']) + .send({ + name: appName, + }); expect(response.statusCode).toBe(403); } @@ -94,7 +98,10 @@ describe('apps controller', () => { const response = await request(app.getHttpServer()) .post(`/api/apps`) .set('tj-workspace-id', adminUserData.user.defaultOrganizationId) - .set('Cookie', adminUserData['tokenCookie']); + .set('Cookie', adminUserData['tokenCookie']) + .send({ + name: appName, + }); expect(response.statusCode).toBe(201); expect(response.body.name).toContain('My app'); @@ -115,10 +122,14 @@ describe('apps controller', () => { await createAppEnvironments(app, adminUserData.organization.id); + const appName = 'My app'; const response = await request(app.getHttpServer()) .post(`/api/apps`) .set('tj-workspace-id', adminUserData.user.defaultOrganizationId) - .set('Cookie', loggedUser.tokenCookie); + .set('Cookie', loggedUser.tokenCookie) + .send({ + name: appName, + }); expect(response.statusCode).toBe(201); expect(response.body.name).toContain('My app'); @@ -535,7 +546,8 @@ describe('apps controller', () => { let response = await request(app.getHttpServer()) .post(`/api/apps/${application.id}/clone`) .set('tj-workspace-id', adminUserData.user.defaultOrganizationId) - .set('Cookie', adminUserData['tokenCookie']); + .set('Cookie', adminUserData['tokenCookie']) + .send({ name: 'App to clone_Copy' }); expect(response.statusCode).toBe(201); @@ -546,14 +558,16 @@ describe('apps controller', () => { response = await request(app.getHttpServer()) .post(`/api/apps/${application.id}/clone`) .set('tj-workspace-id', developerUserData.user.defaultOrganizationId) - .set('Cookie', developerUserData['tokenCookie']); + .set('Cookie', developerUserData['tokenCookie']) + .send({ name: 'App to clone_Copy' }); expect(response.statusCode).toBe(403); response = await request(app.getHttpServer()) .post(`/api/apps/${application.id}/clone`) .set('tj-workspace-id', viewerUserData.user.defaultOrganizationId) - .set('Cookie', viewerUserData['tokenCookie']); + .set('Cookie', viewerUserData['tokenCookie']) + .send({ name: 'App to clone_Copy' }); expect(response.statusCode).toBe(403); @@ -583,7 +597,8 @@ describe('apps controller', () => { const response = await request(app.getHttpServer()) .post(`/api/apps/${application.id}/clone`) .set('tj-workspace-id', anotherOrgAdminUserData.user.defaultOrganizationId) - .set('Cookie', loggedUser.tokenCookie); + .set('Cookie', loggedUser.tokenCookie) + .send({ name: 'name_Copy' }); expect(response.statusCode).toBe(403); @@ -2121,7 +2136,8 @@ describe('apps controller', () => { const response = await request(app.getHttpServer()) .post('/api/apps/import') .set('tj-workspace-id', userData.user.defaultOrganizationId) - .set('Cookie', userData['tokenCookie']); + .set('Cookie', userData['tokenCookie']) + .send({ app: application, name: 'name' }); expect(response.statusCode).toBe(403); } @@ -2130,7 +2146,7 @@ describe('apps controller', () => { .post('/api/apps/import') .set('tj-workspace-id', adminUserData.user.defaultOrganizationId) .set('Cookie', adminUserData['tokenCookie']) - .send({ name: 'Imported App' }); + .send({ app: application, name: 'Imported App' }); expect(response.statusCode).toBe(201); diff --git a/server/test/controllers/library_apps.e2e-spec.ts b/server/test/controllers/library_apps.e2e-spec.ts index 12bb5cdefd..34f4c77b06 100644 --- a/server/test/controllers/library_apps.e2e-spec.ts +++ b/server/test/controllers/library_apps.e2e-spec.ts @@ -34,7 +34,7 @@ describe('library apps controller', () => { let response = await request(app.getHttpServer()) .post('/api/library_apps') - .send({ identifier: 'github-contributors' }) + .send({ identifier: 'github-contributors', appName: 'Github Contributors' }) .set('tj-workspace-id', nonAdminUserData.user.defaultOrganizationId) .set('Cookie', nonAdminUserData['tokenCookie']); @@ -42,12 +42,12 @@ describe('library apps controller', () => { response = await request(app.getHttpServer()) .post('/api/library_apps') - .send({ identifier: 'supply-chain-management' }) + .send({ identifier: 'github-contributors', appName: 'GitHub Contributor Leaderboard' }) .set('tj-workspace-id', adminUserData.user.defaultOrganizationId) .set('Cookie', adminUserData['tokenCookie']); expect(response.statusCode).toBe(201); - expect(response.body.app[0].name).toContain('Supply Chain Management'); + expect(response.body.app[0].name).toContain('GitHub Contributor Leaderboard'); }); it('should return error if template identifier is not found', async () => { @@ -61,7 +61,7 @@ describe('library apps controller', () => { const response = await request(app.getHttpServer()) .post('/api/library_apps') - .send({ identifier: 'non-existent-template' }) + .send({ identifier: 'non-existent-template', appName: 'Non existent template' }) .set('tj-workspace-id', adminUserData.user.defaultOrganizationId) .set('Cookie', adminUserData['tokenCookie']); diff --git a/server/test/services/app_import_export.service.spec.ts b/server/test/services/app_import_export.service.spec.ts index eea2c15bd0..a3e39ea8e9 100644 --- a/server/test/services/app_import_export.service.spec.ts +++ b/server/test/services/app_import_export.service.spec.ts @@ -164,7 +164,8 @@ describe('AppImportExportService', () => { groups: ['all_users', 'admin'], }); const adminUser = adminUserData.user; - await expect(service.import(adminUser, 'hello world')).rejects.toThrow('Invalid params for app import'); + const appName = 'my app'; + await expect(service.import(adminUser, 'hello world', appName)).rejects.toThrow('Invalid params for app import'); }); it('should import app with empty related associations', async () => { @@ -180,8 +181,8 @@ describe('AppImportExportService', () => { }); const { appV2: exportedApp } = await service.export(adminUser, app.id); - - const result = await service.import(adminUser, exportedApp); + const appName = 'my app'; + const result = await service.import(adminUser, exportedApp, appName); const importedApp = await getAppWithAllDetails(result.id); expect(importedApp.id == exportedApp.id).toBeFalsy(); @@ -261,7 +262,8 @@ describe('AppImportExportService', () => { }); const { appV2: exportedApp } = await service.export(adminUser, application.id); - const result = await service.import(adminUser, exportedApp); + const appName = 'my app'; + const result = await service.import(adminUser, exportedApp, appName); const importedApp = await getAppWithAllDetails(result.id); expect(importedApp.id == exportedApp.id).toBeFalsy();