Merge branch 'main' into appdefinition-architecture-revamp

This commit is contained in:
arpitnath 2023-10-23 17:48:15 +05:30
commit 5c379bd45f
193 changed files with 4763 additions and 2270 deletions

View file

@ -144,7 +144,7 @@ jobs:
run: docker buildx use mybuilder
- name: Build docker image
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypress
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypressplaform
- name: Set up environment variables
run: |

View file

@ -38,7 +38,7 @@ jobs:
run: docker buildx use mybuilder
- name: Build docker image
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypress
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypressplaform
- name: Set up environment variables
run: |
@ -131,7 +131,7 @@ jobs:
run: docker buildx use mybuilder
- name: Build docker image
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypress
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypressplaform
- name: Set up environment variables
run: |

View file

@ -148,7 +148,7 @@ jobs:
run: docker buildx use mybuilder
- name: Build docker image
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypress
run: docker buildx build --platform=linux/amd64 -f docker/production.Dockerfile . -t tooljet/tj-osv:cypressplaform
- name: Set up environment variables
run: |

222
.github/workflows/docs-pr-app.yml vendored Normal file
View file

@ -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)
}

View file

@ -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']
})
}

View file

@ -1 +1 @@
2.20.1
2.22.0

View file

@ -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:

View file

@ -83,7 +83,7 @@ module.exports = defineConfig({
"cypress/e2e/editor/app-version/version.cy.js"
],
numTestsKeptInMemory: 1,
redirectionLimit: 7,
redirectionLimit: 15,
experimentalRunAllSpecs: true,
experimentalMemoryManagement: true,
video: false,

View file

@ -79,8 +79,8 @@ module.exports = defineConfig({
baseUrl: "http://localhost:8082",
specPattern: "cypress/e2e/**/*.cy.js",
downloadsFolder: "cypress/downloads",
numTestsKeptInMemory: 10,
redirectionLimit: 5,
numTestsKeptInMemory: 0,
redirectionLimit: 7,
experimentalRunAllSpecs: true,
trashAssetsBeforeRuns: true,
experimentalMemoryManagement: true,

View file

@ -1,6 +1,10 @@
Cypress.Commands.add(
"apiLogin",
(userEmail = "dev@tooljet.io", userPassword = "password", workspaceId = '') => {
(
userEmail = "dev@tooljet.io",
userPassword = "password",
workspaceId = ""
) => {
cy.request({
url: `http://localhost:3000/api/authenticate/${workspaceId}`,
method: "POST",
@ -138,4 +142,24 @@ Cypress.Commands.add(
// ]
// );
Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => {
cy.getCookie("tj_auth_token").then((cookie) => {
cy.request(
{
method: "POST",
url: "http://localhost:3000/api/organizations",
headers: {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
},
body: {
name: workspaceName,
slug: workspaceSlug,
},
},
{ log: false }
).then((response) => {
expect(response.status).to.equal(201);
});
});
});

View file

@ -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();

View file

@ -327,10 +327,10 @@ export const commonWidgetSelector = {
modalHeader: '[data-cy="modal-header"]',
makePublicAppToggleLabel: '[data-cy="make-public-app-label"]',
shareableAppLink: '[data-cy="shareable-app-link-label"]',
copyAppLinkButton: '[data-cy="copy-app-link-button"]',
// iframeLinkLabel: '[data-cy="iframe-link-label"]',
// ifameLinkCopyButton: '[data-cy="iframe-link-copy-button"]',
},
copyAppLinkButton: '.input-group > :nth-child(3)',
makePublicAppToggle: '[data-cy="make-public-app-toggle"]',
appLink: '[data-cy="app-link"]',
appNameSlugInput: '[data-cy="app-name-slug-input"]',

View file

@ -164,9 +164,8 @@ export const commonText = {
shareModalElements: {
modalHeader: "Share",
makePublicAppToggleLabel: "Make application public?",
shareableAppLink: "Get shareable link for this application",
copyAppLinkButton: "copy",
makePublicAppToggleLabel: "Make application public",
shareableAppLink: "Shareable app link",
// iframeLinkLabel: "Get embeddable link for this application",
// ifameLinkCopyButton: "copy",
},

View file

@ -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",

View file

@ -20,5 +20,5 @@ export const exportAppModalText = {
export const importText = {
importOption: "Import",
couldNotImportAppToastMessage: `Could not import: SyntaxError: Unexpected token`,
appImportedToastMessage: "Imported successfully.",
appImportedToastMessage: "App imported successfully.",
};

View file

@ -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

View file

@ -19,17 +19,16 @@ import {
} from "Texts/common";
describe("Editor- Global Settings", () => {
const data = {};
beforeEach(() => {
data.appName = `${fake.companyName}-App`;
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(data.appName);
cy.openApp();
});
it("should verify global settings", () => {
const data = {};
data.backgroundColor = fake.randomRgba;
data.appName = `${fake.companyName}-App`;
cy.renameApp(data.appName);
cy.get("[data-cy='left-sidebar-settings-button']").click();
cy.get('[data-cy="label-global settings"]').verifyVisibleElement(

View file

@ -1,3 +1,4 @@
import { fake } from "Fixtures/fake";
import {
verifyMultipleComponentValuesFromInspector,
verifyComponentValueFromInspector,
@ -15,7 +16,7 @@ import { multipageSelector } from "Selectors/multipage";
describe("Editor- Inspector", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.openApp();
});

View file

@ -41,13 +41,12 @@ import {
describe("Multipage", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.openApp();
});
it("should verify the elements on multipage", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.widgetName = fake.widgetName;
data.tooltipText = fake.randomSentence;
data.minimumLength = randomNumber(1, 4);

View file

@ -64,7 +64,7 @@ import { deleteDownloadsFolder } from "Support/utils/common";
describe("RunJS", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.openApp();
cy.viewport(1800, 1800);
cy.dragAndDropWidget("Button");

View file

@ -63,7 +63,7 @@ import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector";
describe("runpy", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.openApp();
cy.viewport(1800, 1800);
cy.dragAndDropWidget("Button");
@ -214,7 +214,10 @@ actions.unsetPageVariable('pageVar')`
cy.wait(200);
cy.waitForAutoSave();
query("run");
cy.get('[data-cy="sign-in-header"]').should("be.visible");
cy.get('[data-cy="sign-in-header"]', { timeout: 20000 }).should(
"be.visible"
);
});
it("should verify global and page data", () => {

View file

@ -56,7 +56,7 @@ import { resizeQueryPanel } from "Support/utils/dataSource";
describe("Table", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.openApp();
deleteDownloadsFolder();
cy.viewport(1400, 2200);

View file

@ -61,14 +61,16 @@ describe("App Import Functionality", () => {
cy.get(importSelectors.importOptionInput).selectFile(appFile, {
force: true,
});
cy.verifyToastMessage(
commonSelectors.toastMessage,
importText.appImportedToastMessage
);
cy.get('[data-cy="import-app-title"]').should("be.visible");
cy.get('[data-cy="Import app"]').click();
cy.get(".go3958317564")
.should("be.visible")
.and("have.text", importText.appImportedToastMessage);
cy.get(".driver-close-btn").click();
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
appData.name
appData.name.toLowerCase()
);
cy.modifyCanvasSize(900, 600);
cy.dragAndDropWidget(buttonText.defaultWidgetText);
@ -107,10 +109,12 @@ describe("App Import Functionality", () => {
cy.get(importSelectors.importOptionInput).selectFile(exportedFilePath, {
force: true,
});
cy.verifyToastMessage(
commonSelectors.toastMessage,
importText.appImportedToastMessage
);
cy.get('[data-cy="import-app-title"]').should("be.visible");
cy.get('[data-cy="Import app"]').click();
cy.get(".go3958317564")
.should("be.visible")
.and("have.text", importText.appImportedToastMessage);
cy.get(
`[data-cy="draggable-widget-${buttonText.defaultWidgetName}"]`
).should("be.visible");
@ -119,7 +123,7 @@ describe("App Import Functionality", () => {
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
exportedAppData.app[0].definition.appV2.name
exportedAppData.app[0].definition.appV2.name.toLowerCase()
);
cy.get(
appVersionSelectors.currentVersionField((currentVersion = "v1"))
@ -178,10 +182,11 @@ describe("App Import Functionality", () => {
force: true,
}
);
cy.verifyToastMessage(
commonSelectors.toastMessage,
importText.appImportedToastMessage
);
cy.get('[data-cy="import-app-title"]').should("be.visible");
cy.get('[data-cy="Import app"]').click();
cy.get(".go3958317564")
.should("be.visible")
.and("have.text", importText.appImportedToastMessage);
cy.get(appVersionSelectors.appVersionMenuField).click();
cy.get(appVersionSelectors.appVersionContentList).should(
"have.text",
@ -195,7 +200,7 @@ describe("App Import Functionality", () => {
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value",
exportedAppData.app[0].definition.appV2.name
exportedAppData.app[0].definition.appV2.name.toLowerCase()
);
cy.get(
appVersionSelectors.currentVersionField(

View file

@ -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,

View file

@ -160,9 +160,11 @@ describe("Manage SSO for multi workspace", () => {
if (envVar === "Community") {
it("Should verify the workspace login page", () => {
data.workspaceName = fake.companyName;
common.createWorkspace(data.workspaceName);
data.workspaceName = fake.companyName.toLowerCase();
cy.apiLogin()
cy.apiCreateWorkspace(data.workspaceName, data.workspaceName)
cy.visit(data.workspaceName)
cy.wait(500)
common.navigateToManageSSO();
SSO.visitWorkspaceLoginPage();
SSO.workspaceLoginPageElements(data.workspaceName);
@ -243,10 +245,11 @@ describe("Manage SSO for multi workspace", () => {
cy.notVisible(commonSelectors.passwordInputField);
cy.notVisible(commonSelectors.loginButton);
data.workspaceName = fake.companyName;
cy.appUILogin();
common.createWorkspace(data.workspaceName);
cy.wait(300);
data.workspaceName = fake.companyName.toLowerCase();
cy.apiLogin()
cy.apiCreateWorkspace(data.workspaceName, data.workspaceName)
cy.visit(data.workspaceName)
cy.wait(500)
SSO.disableDefaultSSO();
cy.get(ssoSelector.passwordEnableToggle).uncheck();
cy.get(commonSelectors.buttonSelector("Yes")).click();

View file

@ -13,119 +13,125 @@ 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", () => {
before(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.openApp();
cy.renameApp(data.appName);
cy.dragAndDropWidget("Table", 250, 250);
cy.apiCreateApp(data.appName);
cy.visit('/')
logout();
})
cy.get(commonWidgetSelector.shareAppButton).click();
if (envVar === "Community") {
it("Verify private and public app share funtionality", () => {
cy.openApp(data.appName);
cy.dragAndDropWidget("Table", 250, 250);
for (const elements in commonWidgetSelector.shareModalElements) {
cy.get(
commonWidgetSelector.shareModalElements[elements]
).verifyVisibleElement(
cy.get(commonWidgetSelector.shareAppButton).click();
for (const elements in commonWidgetSelector.shareModalElements) {
cy.get(
commonWidgetSelector.shareModalElements[elements]
).verifyVisibleElement(
"have.text",
commonText.shareModalElements[elements]
);
}
cy.get(commonWidgetSelector.copyAppLinkButton).should("be.visible");
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();
});
}
});

View file

@ -19,15 +19,12 @@ data.folderName = `${fake.companyName.toLowerCase()}-folder`;
describe("User permissions", () => {
before(() => {
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=").as("homePage");
cy.appUILogin();
cy.apiLogin();
cy.apiCreateApp(data.appName);
cy.visit('/')
permissions.reset();
cy.get(commonSelectors.homePageLogo).click();
cy.wait("@homePage");
cy.createApp();
cy.renameApp(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 +38,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 +113,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 +148,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();

View file

@ -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}`);

View file

@ -207,6 +207,8 @@ export const createWorkspace = (workspaceName) => {
cy.get(commonSelectors.workspaceName).click();
cy.get(commonSelectors.addWorkspaceButton).click();
cy.clearAndType(commonSelectors.workspaceNameInput, workspaceName);
cy.clearAndType('[data-cy="workspace-slug-input-field"]', workspaceName);
cy.wait(1000)
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=").as("homePage");
cy.get(commonSelectors.createWorkspaceButton).click();
cy.wait("@homePage");

View file

@ -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.
<div style={{textAlign: 'center'}}>

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/bigquery/bq-query.png" alt="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.
- To delete a table.

View file

@ -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
</div>
## 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` |

View file

@ -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/).
<img className="screenshot-full" src="/img/datasource-reference/couchdb/auth_couch.gif" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/creating.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/updating.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/find.png" alt="Couch find" />
#### Example body:
#### Example Body:
```json
{
@ -239,11 +232,11 @@ Example response from CouchDb:
<img className="screenshot-full" src="/img/datasource-reference/couchdb/find_response.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/get_view.png" alt="Couch get view" />
#### Optional parameters:
#### Optional Parameters:
- **Start key**
- **End key**

View file

@ -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.
<div style={{textAlign: 'center'}}>

View file

@ -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:
</div>
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.
<img className="screenshot-full" src="/img/datasource-reference/elasticsearch/ssl.png" alt="Elastic ssl" />

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/firestore/add-ds-firestore.gif" alt="firestore add ds"/>
@ -160,4 +162,4 @@ The Firestore query result is in the form of object so well need to transform
```js
return data = Array(data)
```
```

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/gcs-connect.png" alt="gcs connection" />

View file

@ -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 <b>Multiple recipients</b> 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.
:::

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/minio-query.png" alt="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)

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/mo-query.png" alt="ToolJet - Mongo query" height="250"/>
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

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/mssql/query.gif" alt="ToolJet - Redis connection" height="420"/>

View file

@ -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.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/mysql/addmysql.gif" alt="MySQL datasource"/>
<img className="screenshot-full" src="/img/datasource-reference/mysql/addmysql.gif" alt="MySQL data source"/>
</div>
<br/>
@ -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.
<div style={{textAlign: 'center'}}>
@ -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).
:::
:::

View file

@ -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).
<div style={{textAlign: 'center'}}>
@ -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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/querying.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/share.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/db_q.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/db_retrieve.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/page_q.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/block_q.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/user_q.png" alt="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**

View file

@ -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/)**.
:::

View file

@ -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.

View file

@ -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.

View file

@ -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.
<div style={{textAlign: 'center'}}>

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/bigquery/bq-query.png" alt="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.
- To delete a table.

View file

@ -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
</div>
## 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` |

View file

@ -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/).
<img className="screenshot-full" src="/img/datasource-reference/couchdb/auth_couch.gif" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/creating.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/updating.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/find.png" alt="Couch find" />
#### Example body:
#### Example Body:
```json
{
@ -239,11 +232,11 @@ Example response from CouchDb:
<img className="screenshot-full" src="/img/datasource-reference/couchdb/find_response.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/get_view.png" alt="Couch get view" />
#### Optional parameters:
#### Optional Parameters:
- **Start key**
- **End key**

View file

@ -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.
<div style={{textAlign: 'center'}}>

View file

@ -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:
</div>
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.
<img className="screenshot-full" src="/img/datasource-reference/elasticsearch/ssl.png" alt="Elastic ssl" />

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/firestore/add-ds-firestore.gif" alt="firestore add ds"/>
@ -160,4 +161,4 @@ The Firestore query result is in the form of object so well need to transform
```js
return data = Array(data)
```
```

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/gcs-connect.png" alt="gcs connection" />

View file

@ -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 <b>Multiple recipients</b> 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.
:::

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/minio-query.png" alt="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)

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/mo-query.png" alt="ToolJet - Mongo query" height="250"/>
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

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/mssql/query.gif" alt="ToolJet - Redis connection" height="420"/>

View file

@ -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.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/mysql/addmysql.gif" alt="MySQL datasource"/>
<img className="screenshot-full" src="/img/datasource-reference/mysql/addmysql.gif" alt="MySQL data source"/>
</div>
<br/>
@ -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.
<div style={{textAlign: 'center'}}>
@ -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).
:::
:::

View file

@ -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).
<div style={{textAlign: 'center'}}>
@ -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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/querying.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/share.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/db_q.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/db_retrieve.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/page_q.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/block_q.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/user_q.png" alt="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**

View file

@ -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/)**.
:::

View file

@ -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.

View file

@ -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.

View file

@ -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.
<div style={{textAlign: 'center'}}>

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/bigquery/bq-query.png" alt="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.
- To delete a table.

View file

@ -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
</div>
## 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` |

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/creating.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/updating.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/find.png" alt="Couch find" />
#### Example body:
#### Example Body:
```json
{
@ -239,11 +235,11 @@ Example response from CouchDb:
<img className="screenshot-full" src="/img/datasource-reference/couchdb/find_response.png" alt="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
<img className="screenshot-full" src="/img/datasource-reference/couchdb/get_view.png" alt="Couch get view" />
#### Optional parameters:
#### Optional Parameters:
- **Start key**
- **End key**

View file

@ -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.
<div style={{textAlign: 'center'}}>

View file

@ -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:
</div>
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.
<img className="screenshot-full" src="/img/datasource-reference/elasticsearch/ssl.png" alt="Elastic ssl" />

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/firestore/add-ds-firestore.gif" alt="firestore add ds"/>
@ -160,4 +162,4 @@ The Firestore query result is in the form of object so well need to transform
```js
return data = Array(data)
```
```

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/gcs-connect.png" alt="gcs connection" />

View file

@ -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 <b>Multiple recipients</b> 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.
:::

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/minio-query.png" alt="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)

View file

@ -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
<img className="screenshot-full" src="/img/datasource-reference/mo-query.png" alt="ToolJet - Mongo query" height="250"/>
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

View file

@ -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.
<img className="screenshot-full" src="/img/datasource-reference/mssql/query.gif" alt="ToolJet - Redis connection" height="420"/>

View file

@ -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.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/datasource-reference/mysql/addmysql.gif" alt="MySQL datasource"/>
<img className="screenshot-full" src="/img/datasource-reference/mysql/addmysql.gif" alt="MySQL data source"/>
</div>
<br/>
@ -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.
<div style={{textAlign: 'center'}}>
@ -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).
:::
:::

View file

@ -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).
<div style={{textAlign: 'center'}}>
@ -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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/querying.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/share.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/db_q.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/db_retrieve.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/page_q.png" alt="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)**
<img className="screenshot-full" src="/img/datasource-reference/notion/block_q.png" alt="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.
<img className="screenshot-full" src="/img/datasource-reference/notion/user_q.png" alt="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**

View file

@ -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/)**.
:::

View file

@ -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.

View file

@ -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.

View file

@ -1 +1 @@
2.20.1
2.22.0

View file

@ -117,10 +117,10 @@
"preview": "Preview",
"share": "Share",
"shareModal": {
"makeApplicationPublic": "Make application public?",
"shareableLink": "Get shareable link for this application",
"makeApplicationPublic": "Make application public",
"shareableLink": "Shareable app link",
"copy": "copy",
"embeddableLink": "Get embeddable link for this application",
"embeddableLink": "Embedded app link",
"manageUsers": "Users"
},
"appVersionManager": {
@ -234,7 +234,7 @@
"addNewWorkSpace": "Add new workspace",
"loadOrganizations": "Load Organizations",
"createWorkspace": "Create workspace",
"workspaceName": "workspace name",
"workspaceName": "Workspace name",
"editWorkspace": "Edit workspace",
"menus": {
"addWorkspace": "Add workspace",
@ -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"
}
}
}
}

View file

@ -1,9 +1,10 @@
import React from 'react';
import { buildURLWithQuery } from '@/_helpers/utils';
export default function GitSSOLoginButton({ configs, text }) {
export default function GitSSOLoginButton({ configs, text, setRedirectUrlToCookie }) {
const gitLogin = (e) => {
e.preventDefault();
setRedirectUrlToCookie && setRedirectUrlToCookie();
window.location.href = buildURLWithQuery(`${configs.host_name || 'https://github.com'}/login/oauth/authorize`, {
client_id: configs?.client_id,
scope: 'user:email',

View file

@ -12,6 +12,7 @@ export default function GoogleSSOLoginButton(props) {
};
const googleLogin = (e) => {
e.preventDefault();
props.setRedirectUrlToCookie && props.setRedirectUrlToCookie();
const { client_id } = props.configs;
const authUrl = buildURLWithQuery('https://accounts.google.com/o/oauth2/auth', {
redirect_uri: `${window.public_config?.TOOLJET_HOST}${window.public_config?.SUB_PATH ?? '/'}sso/google${

View file

@ -1,16 +1,8 @@
import React, { Suspense } from 'react';
// eslint-disable-next-line no-unused-vars
import config from 'config';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import {
getWorkspaceIdFromURL,
appendWorkspaceId,
stripTrailingSlash,
getSubpath,
pathnameWithoutSubpath,
} from '@/_helpers/utils';
import { authenticationService, tooljetService, organizationService } from '@/_services';
import { authorizeWorkspace } from '@/_helpers/authorizeWorkspace';
import { authenticationService, tooljetService } from '@/_services';
import { withRouter } from '@/_hoc/withRouter';
import { PrivateRoute, AdminRoute } from '@/_components';
import { HomePage } from '@/HomePage';
@ -36,6 +28,7 @@ import { AppLoader } from '@/AppLoader';
import SetupScreenSelfHost from '../SuccessInfoScreen/SetupScreenSelfHost';
export const BreadCrumbContext = React.createContext({});
import 'react-tooltip/dist/react-tooltip.css';
import { getWorkspaceIdOrSlugFromURL } from '@/_helpers/routes';
const AppWrapper = (props) => {
return (
@ -69,59 +62,8 @@ class AppComponent extends React.Component {
});
};
isThisExistedRoute = () => {
const existedPaths = [
'forgot-password',
'reset-password',
'invitations',
'organization-invitations',
'setup',
'confirm',
'confirm-invite',
];
const subpath = getSubpath();
const subpathArray = subpath ? subpath.split('/').filter((path) => path != '') : [];
const pathnames = window.location.pathname.split('/')?.filter((path) => path != '');
const checkPath = () => existedPaths.find((path) => pathnames[subpath ? subpathArray.length : 0] === path);
return pathnames?.length > 0 ? (checkPath() ? true : false) : false;
};
componentDidMount() {
if (!this.isThisExistedRoute()) {
const workspaceId = getWorkspaceIdFromURL();
if (workspaceId) {
this.authorizeUserAndHandleErrors(workspaceId);
} else {
const isApplicationsPath = window.location.pathname.includes('/applications/');
const appId = isApplicationsPath ? pathnameWithoutSubpath(window.location.pathname).split('/')[2] : null;
authenticationService
.validateSession(appId)
.then(({ current_organization_id }) => {
//check if the page is not switch-workspace, if then redirect to the page
if (window.location.pathname !== `${getSubpath() ?? ''}/switch-workspace`) {
this.authorizeUserAndHandleErrors(current_organization_id);
} else {
this.updateCurrentSession({
current_organization_id,
});
}
})
.catch(() => {
if (!this.isThisWorkspaceLoginPage(true) && !isApplicationsPath) {
this.updateCurrentSession({
authentication_status: false,
});
} else if (isApplicationsPath) {
this.updateCurrentSession({
authentication_failed: true,
load_app: true,
});
}
});
}
}
authorizeWorkspace();
this.fetchMetadata();
setInterval(this.fetchMetadata, 1000 * 60 * 60 * 1);
}
@ -136,8 +78,8 @@ class AppComponent extends React.Component {
componentDidUpdate(prevProps) {
// Check if the current location is the dashboard (homepage)
if (
this.props.location.pathname === `/${getWorkspaceIdFromURL()}` &&
prevProps.location.pathname !== `/${getWorkspaceIdFromURL()}` &&
this.props.location.pathname === `/${getWorkspaceIdOrSlugFromURL()}` &&
prevProps.location.pathname !== `/${getWorkspaceIdOrSlugFromURL()}` &&
this.checkPreviousRoute(prevProps.location.pathname) &&
prevProps.location.pathname !== `/:workspaceId`
) {
@ -146,93 +88,6 @@ class AppComponent extends React.Component {
}
}
isThisWorkspaceLoginPage = (justLoginPage = false) => {
const subpath = window?.public_config?.SUB_PATH ? stripTrailingSlash(window?.public_config?.SUB_PATH) : null;
const pathname = location.pathname.replace(subpath, '');
const pathnames = pathname.split('/').filter((path) => path !== '');
return (justLoginPage && pathnames[0] === 'login') || (pathnames.length === 2 && pathnames[0] === 'login');
};
authorizeUserAndHandleErrors = (workspaceId) => {
const subpath = getSubpath();
this.updateCurrentSession({
current_organization_id: workspaceId,
});
authenticationService
.authorize()
.then((data) => {
organizationService.getOrganizations().then((response) => {
const current_organization_name = response.organizations.find((org) => org.id === workspaceId)?.name;
// this will add the other details like permission and user previlliage details to the subject
this.updateCurrentSession({
...data,
current_organization_name,
organizations: response.organizations,
load_app: true,
});
// if user is trying to load the workspace login page, then redirect to the dashboard
if (this.isThisWorkspaceLoginPage())
return (window.location = appendWorkspaceId(workspaceId, '/:workspaceId'));
});
})
.catch((error) => {
// if the auth token didn't contain workspace-id, try switch workspace fn
if (error && error?.data?.statusCode === 401) {
//get current session workspace id
authenticationService
.validateSession()
.then(({ current_organization_id }) => {
// change invalid or not authorized org id to previous one
this.updateCurrentSession({
current_organization_id,
});
organizationService
.switchOrganization(workspaceId)
.then((data) => {
this.updateCurrentSession(data);
if (this.isThisWorkspaceLoginPage())
return (window.location = appendWorkspaceId(workspaceId, '/:workspaceId'));
this.authorizeUserAndHandleErrors(workspaceId);
})
.catch(() => {
organizationService.getOrganizations().then((response) => {
const current_organization_name = response.organizations.find(
(org) => org.id === current_organization_id
)?.name;
this.updateCurrentSession({
current_organization_name,
load_app: true,
});
if (!this.isThisWorkspaceLoginPage())
return (window.location = `${subpath ?? ''}/login/${workspaceId}`);
});
});
})
.catch(() => this.logout());
} else if ((error && error?.data?.statusCode == 422) || error?.data?.statusCode == 404) {
window.location = subpath ? `${subpath}${'/switch-workspace'}` : '/switch-workspace';
} else {
if (!this.isThisWorkspaceLoginPage() && !this.isThisWorkspaceLoginPage(true))
this.updateCurrentSession({
authentication_status: false,
});
}
});
};
updateCurrentSession = (newSession) => {
const currentSession = authenticationService.currentSessionValue;
authenticationService.updateCurrentSession({ ...currentSession, ...newSession });
};
logout = () => {
authenticationService.logout();
};
switchDarkMode = (newMode) => {
this.setState({ darkMode: newMode });
localStorage.setItem('darkMode', newMode);
@ -314,22 +169,13 @@ class AppComponent extends React.Component {
/>
<Route
exact
path="/:workspaceId/apps/:id/:pageHandle?/*"
path="/:workspaceId/apps/:slug/:pageHandle?/*"
element={
<PrivateRoute>
<AppLoader switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route
exact
path="/applications/:id/versions/:versionId/:pageHandle?"
element={
<PrivateRoute>
<Viewer switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</PrivateRoute>
}
/>
<Route
exact
path="/applications/:slug/:pageHandle?"

View file

@ -1,80 +1,32 @@
import React, { useEffect } from 'react';
import { withTranslation } from 'react-i18next';
import { appService, organizationService, authenticationService } from '@/_services';
import { Editor, EditorFunc } from '@/Editor';
import { Editor } from '../Editor/Editor';
import { RealtimeEditor } from '@/Editor/RealtimeEditor';
import config from 'config';
import { safelyParseJSON, stripTrailingSlash, redirectToDashboard, getSubpath, getWorkspaceId } from '@/_helpers/utils';
import { toast } from 'react-hot-toast';
import { useParams } from 'react-router-dom';
import { appService } from '@/_services';
import { useAppDataActions } from '@/_stores/appDataStore';
import _ from 'lodash';
const AppLoaderComponent = (props) => {
const params = useParams();
const appId = params.id;
const AppLoaderComponent = React.memo((props) => {
const [shouldLoadApp, setShouldLoadApp] = React.useState(false);
const { updateState } = useAppDataActions();
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => loadAppDetails(), []);
useEffect(() => {
props?.id && props?.slug && loadAppDetails(props?.id);
const loadAppDetails = () => {
appService
.fetchApp(appId, 'edit')
.then((data) => {
setShouldLoadApp(true);
updateState({
app: data,
});
})
.catch((error) => {
handleError(error);
return () => {
setShouldLoadApp(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loadAppDetails = (appId) => {
appService.fetchApp(appId, 'edit').then((data) => {
setShouldLoadApp(true);
updateState({
app: data,
appId: data.id,
});
};
const switchOrganization = (orgId) => {
const path = `/apps/${appId}`;
const sub_path = window?.public_config?.SUB_PATH ? stripTrailingSlash(window?.public_config?.SUB_PATH) : '';
organizationService.switchOrganization(orgId).then(
() => {
window.location.href = `${sub_path}/${orgId}${path}`;
},
() => {
return (window.location.href = `${sub_path}/login/${orgId}?redirectTo=${path}`);
}
);
};
const handleError = (error) => {
try {
if (error?.data) {
const statusCode = error.data?.statusCode;
if (statusCode === 403) {
const errorObj = safelyParseJSON(error.data?.message);
if (
errorObj?.organizationId &&
authenticationService.currentSessionValue.current_organization_id !== errorObj?.organizationId
) {
switchOrganization(errorObj?.organizationId);
return;
}
redirectToDashboard();
} else if (statusCode === 401) {
window.location = `${getSubpath() ?? ''}/login${
!_.isEmpty(getWorkspaceId()) ? `/${getWorkspaceId()}` : ''
}?redirectTo=${this.props.location.pathname}`;
return;
} else if (statusCode === 404 || statusCode === 422) {
toast.error(error?.error ?? 'App not found');
}
redirectToDashboard();
}
} catch (err) {
redirectToDashboard();
}
});
};
if (!shouldLoadApp) return <></>;
@ -82,8 +34,8 @@ const AppLoaderComponent = (props) => {
return config.ENABLE_MULTIPLAYER_EDITING ? (
<RealtimeEditor {...props} shouldLoadApp={shouldLoadApp} />
) : (
<EditorFunc {...props} shouldLoadApp={shouldLoadApp} />
<Editor {...props} />
);
};
});
export const AppLoader = withTranslation()(AppLoaderComponent);

View file

@ -5,10 +5,11 @@ import { useSpring, animated } from 'react-spring';
import usePopover from '@/_hooks/use-popover';
import OptionsIcon from './icons/options.svg';
// import OptionsSelectedIcon from './icons/options-selected.svg';
import useRouter from '@/_hooks/use-router';
import { commentsService } from '@/_services';
import { useTranslation } from 'react-i18next';
import { useAppDataStore } from '@/_stores/appDataStore';
import { shallow } from 'zustand/shallow';
const CommentActions = ({
socket,
@ -21,8 +22,13 @@ const CommentActions = ({
}) => {
const [open, trigger, content, setOpen] = usePopover(false);
const popoverFadeStyle = useSpring({ opacity: open ? 1 : 0 });
const router = useRouter();
const { t } = useTranslation();
const { appId } = useAppDataStore(
(state) => ({
appId: state?.appId,
}),
shallow
);
const handleDelete = async () => {
await commentsService.deleteComment(commentId);
@ -31,7 +37,7 @@ const CommentActions = ({
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'notifications', appId: router.query.id },
data: { message: 'notifications', appId },
})
);
};

View file

@ -7,14 +7,12 @@ import { commentsService } from '@/_services';
import { pluralize } from '@/_helpers/utils';
import Spinner from '@/_ui/Spinner';
import useRouter from '@/_hooks/use-router';
import UnResolvedIcon from './icons/unresolved.svg';
import ResolvedIcon from './icons/resolved.svg';
const CommentHeader = ({ socket, count = 0, threadId, isResolved, isThreadOwner, fetchThreads, close }) => {
const CommentHeader = ({ socket, count = 0, threadId, isResolved, isThreadOwner, fetchThreads, close, appId }) => {
const [spinning, setSpinning] = React.useState(false);
const router = useRouter();
const handleResolved = async () => {
setSpinning(true);
@ -24,7 +22,7 @@ const CommentHeader = ({ socket, count = 0, threadId, isResolved, isThreadOwner,
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'notifications', appId: router.query.id },
data: { message: 'notifications', appId },
})
);
if (!isResolved) {
@ -41,7 +39,7 @@ const CommentHeader = ({ socket, count = 0, threadId, isResolved, isThreadOwner,
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'notifications', appId: router.query.id },
data: { message: 'notifications', appId },
})
);
};

View file

@ -11,8 +11,20 @@ import { commentsService, organizationService, authenticationService } from '@/_
import useRouter from '@/_hooks/use-router';
import DOMPurify from 'dompurify';
import { capitalize } from 'lodash';
import { getPathname } from '@/_helpers/routes';
const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads, appVersionsId, canvasWidth }) => {
const Comment = ({
socket,
x,
y,
threadId,
user = {},
isResolved,
fetchThreads,
appVersionsId,
canvasWidth,
appId,
}) => {
const [loading, setLoading] = React.useState(true);
const [editComment, setEditComment] = React.useState('');
const [editCommentId, setEditCommentId] = React.useState('');
@ -60,7 +72,7 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
} else {
// resetting the query param
// react router updates the url with the set basename resulting invalid url unless replaced
router.history(window.location.pathname.replace(window.public_config?.SUB_PATH, '/'));
router.history(getPathname());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
@ -82,13 +94,13 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
socket.send(
JSON.stringify({
event: 'events',
data: { message: threadId, appId: router.query.id },
data: { message: threadId, appId },
})
);
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'notifications', appId: router.query.id },
data: { message: 'notifications', appId },
})
);
fetchData();
@ -100,7 +112,7 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'notifications', appId: router.query.id },
data: { message: 'notifications', appId },
})
);
};
@ -168,6 +180,7 @@ const Comment = ({ socket, x, y, threadId, user = {}, isResolved, fetchThreads,
fetchThreads={fetchThreads}
isThreadOwner={currentUser?.id === user.id}
isResolved={isResolved}
appId={appId}
/>
<CommentBody
socket={socket}

View file

@ -6,6 +6,7 @@ import moment from 'moment';
import useRouter from '@/_hooks/use-router';
import Spinner from '@/_ui/Spinner';
import { getPathname } from '@/_helpers/routes';
const Content = ({ notifications, loading, darkMode }) => {
const router = useRouter();
@ -35,7 +36,7 @@ const Content = ({ notifications, loading, darkMode }) => {
onClick={() => {
router.push({
// react router updates the url with the set basename resulting invalid url unless replaced
pathname: window.location.pathname.replace(window.public_config?.SUB_PATH, '/'),
pathname: getPathname(),
search: `?threadId=${comment.thread.id}&commentId=${comment.id}`,
});
}}

View file

@ -3,9 +3,9 @@ import cx from 'classnames';
import React from 'react';
import { commentsService } from '@/_services';
import TabContent from './Content';
import useRouter from '@/_hooks/use-router';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { useEditorStore } from '@/_stores/editorStore';
import { useAppDataStore } from '@/_stores/appDataStore';
import { shallow } from 'zustand/shallow';
const CommentNotifications = ({ socket, pageId }) => {
@ -22,24 +22,31 @@ const CommentNotifications = ({ socket, pageId }) => {
}),
shallow
);
const { appId } = useAppDataStore(
(state) => ({
appId: state?.appId,
}),
shallow
);
const [notifications, setNotifications] = React.useState([]);
const [loading, setLoading] = React.useState(false);
const [key, setKey] = React.useState('active');
const router = useRouter();
async function fetchData(selectedKey) {
const isResolved = selectedKey === 'resolved';
setLoading(true);
const { data } = await commentsService.getNotifications(router.query.id, isResolved, appVersionsId, pageId);
setLoading(false);
setNotifications(data);
if (appId) {
console.log('inside-CommentNotifications', appId);
const isResolved = selectedKey === 'resolved';
setLoading(true);
const { data } = await commentsService.getNotifications(appId, isResolved, appVersionsId, pageId);
setLoading(false);
setNotifications(data);
}
}
React.useEffect(() => {
fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [appId]);
React.useEffect(() => {
socket?.addEventListener('message', function (event) {

View file

@ -5,15 +5,20 @@ import { isEmpty } from 'lodash';
import Comment from './Comment';
import { commentsService } from '@/_services';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import useRouter from '@/_hooks/use-router';
import { useAppDataStore } from '@/_stores/appDataStore';
const Comments = ({ newThread = {}, socket, canvasWidth, currentPageId }) => {
const [threads, setThreads] = React.useState([]);
const router = useRouter();
const { appVersionsId } = useAppVersionStore((state) => ({ appVersionsId: state?.editingVersion?.id }), shallow);
const { appId } = useAppDataStore(
(state) => ({
appId: state?.appId,
}),
shallow
);
async function fetchData() {
const { data } = await commentsService.getThreads(router.query.id, appVersionsId);
const { data } = await commentsService.getThreads(appId, appVersionsId);
setThreads(data);
}
@ -49,6 +54,7 @@ const Comments = ({ newThread = {}, socket, canvasWidth, currentPageId }) => {
socket={socket}
threadId={id}
canvasWidth={canvasWidth}
appId={appId}
{...thread}
/>
);

View file

@ -763,6 +763,8 @@ export function Table({
if (!serverSidePagination && clientSidePagination) {
setPageSize(rowsPerPage || 10);
}
} else {
setPageSize(rows?.length || 10);
}
}, [clientSidePagination, serverSidePagination, rows, rowsPerPage]);
useEffect(() => {

View file

@ -7,7 +7,6 @@ import { DraggableBox } from './DraggableBox';
import update from 'immutability-helper';
import { componentTypes } from './WidgetManager/components';
import { resolveReferences } from '@/_helpers/utils';
import useRouter from '@/_hooks/use-router';
import Comments from './Comments';
import { commentsService } from '@/_services';
import config from 'config';
@ -18,6 +17,7 @@ import { addComponents, addNewWidgetToTheEditor } from '@/_helpers/appUtils';
import { useCurrentState } from '@/_stores/currentStateStore';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { useEditorStore } from '@/_stores/editorStore';
import { useAppInfo } from '@/_stores/appDataStore';
import { shallow } from 'zustand/shallow';
import _ from 'lodash';
// eslint-disable-next-line import/no-unresolved
@ -60,6 +60,18 @@ export const Container = ({
shallow
);
const { appId } = useAppInfo();
const currentState = useCurrentState();
const { appVersionsId, enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore(
(state) => ({
appVersionsId: state?.editingVersion?.id,
enableReleasedVersionPopupState: state.actions.enableReleasedVersionPopupState,
isVersionReleased: state.isVersionReleased,
}),
shallow
);
const gridWidth = canvasWidth / NO_OF_GRIDS;
const styles = {
width: currentLayout === 'mobile' ? deviceWindowWidth : '100%',
@ -73,16 +85,6 @@ export const Container = ({
[JSON.stringify(appDefinition), currentPageId]
);
const currentState = useCurrentState();
const { appVersionsId, enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore(
(state) => ({
appVersionsId: state?.editingVersion?.id,
enableReleasedVersionPopupState: state.actions.enableReleasedVersionPopupState,
isVersionReleased: state.isVersionReleased,
}),
shallow
);
const [boxes, setBoxes] = useState([]);
const [isDragging, setIsDragging] = useState(false);
const [isResizing, setIsResizing] = useState(false);
@ -91,7 +93,6 @@ export const Container = ({
const [isContainerFocused, setContainerFocus] = useState(false);
const [canvasHeight, setCanvasHeight] = useState(null);
const router = useRouter();
const canvasRef = useRef(null);
const focusedParentIdRef = useRef(undefined);
useHotkeys('meta+z, control+z', () => handleUndo());
@ -452,7 +453,7 @@ export const Container = ({
]);
const { data } = await commentsService.createThread({
appId: router.query.id,
appId,
x: x,
y: e.nativeEvent.offsetY,
appVersionsId,
@ -468,7 +469,7 @@ export const Container = ({
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'threads', appId: router.query.id },
data: { message: 'threads', appId },
})
);
@ -497,7 +498,7 @@ export const Container = ({
},
]);
const { data } = await commentsService.createThread({
appId: router.query.id,
appId,
x,
y: y - 130,
appVersionsId,
@ -513,7 +514,7 @@ export const Container = ({
socket.send(
JSON.stringify({
event: 'events',
data: { message: 'threads', appId: router.query.id },
data: { message: 'threads', appId },
})
);

View file

@ -1,6 +1,6 @@
import React from 'react';
import {
appService,
appsService,
authenticationService,
appVersionService,
orgEnvironmentVariableService,
@ -8,8 +8,7 @@ import {
} from '@/_services';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { defaults, cloneDeep, isEqual, isEmpty, debounce, omit } from 'lodash';
import { shallow } from 'zustand/shallow';
import _, { defaults, cloneDeep, isEqual, isEmpty, debounce, omit } from 'lodash';
import { Container } from './Container';
import { EditorKeyHooks } from './EditorKeyHooks';
import { CustomDragLayer } from './CustomDragLayer';
@ -60,6 +59,7 @@ import { useAppDataStore } from '@/_stores/appDataStore';
import { useCurrentStateStore, useCurrentState } from '@/_stores/currentStateStore';
import { resetAllStores } from '@/_stores/utils';
import { setCookie } from '@/_helpers/cookie';
import { shallow } from 'zustand/shallow';
setAutoFreeze(false);
enablePatches();
@ -67,21 +67,16 @@ enablePatches();
class EditorComponent extends React.Component {
constructor(props) {
super(props);
resetAllStores();
const appId = this.props.params.id;
resetAllStores();
const appId = props.id;
useAppDataStore.getState().actions.setAppId(appId);
useEditorStore.getState().actions.setIsEditorActive(true);
const { socket } = createWebsocketConnection(appId);
this.renameQueryNameId = React.createRef();
this.socket = socket;
this.renameQueryNameId = React.createRef();
const defaultPageId = uuid();
this.subscription = null;
this.defaultDefinition = {
showViewerNavigation: true,
homePageId: defaultPageId,
@ -199,7 +194,6 @@ class EditorComponent extends React.Component {
threshold: 0,
},
});
const globals = {
...this.props.currentState.globals,
theme: { name: this.props.darkMode ? 'dark' : 'light' },
@ -341,7 +335,7 @@ class EditorComponent extends React.Component {
const newState = !this.state.app.is_maintenance_on;
// eslint-disable-next-line no-unused-vars
appService.setMaintenance(this.state.app.id, newState).then((data) => {
appsService.setMaintenance(this.state.app.id, newState).then((data) => {
this.setState({
app: {
...this.state.app,
@ -358,7 +352,7 @@ class EditorComponent extends React.Component {
};
fetchApps = (page) => {
appService.getAll(page).then((data) =>
appsService.getAll(page).then((data) =>
this.setState({
apps: data.apps,
})
@ -366,7 +360,7 @@ class EditorComponent extends React.Component {
};
fetchApp = (startingPageHandle) => {
const appId = this.props.params.id;
const appId = this.props.id;
const callBack = async (data) => {
let dataDefinition = defaults(data.definition, this.defaultDefinition);
@ -424,7 +418,7 @@ class EditorComponent extends React.Component {
isLoading: true,
},
() => {
appService.getApp(appId).then(callBack);
appsService.getApp(appId).then(callBack);
}
);
};
@ -1399,7 +1393,11 @@ class EditorComponent extends React.Component {
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
this.props.navigate(`/${getWorkspaceId()}/apps/${this.state.appId}/${handle}?${queryParamsString}`);
this.props.navigate(`/${getWorkspaceId()}/apps/${this.state.slug}/${handle}?${queryParamsString}`, {
state: {
isSwitchingPage: true,
},
});
const { globals: existingGlobals } = this.props.currentState;
@ -1511,8 +1509,11 @@ class EditorComponent extends React.Component {
const selectedComponents = this?.props?.selectedComponents;
const currentState = this.props?.currentState;
const editingVersion = this.props?.editingVersion;
const previewQuery = queryString.stringify({ version: editingVersion?.name });
const appVersionPreviewLink = editingVersion
? `/applications/${app.id}/versions/${editingVersion.id}/${currentState.page.handle}`
? `/applications/${slug || appId}/${currentState.page.handle}${
!_.isEmpty(previewQuery) ? `?${previewQuery}` : ''
}`
: '';
return (
<div className="editor wrapper">
@ -1605,6 +1606,8 @@ class EditorComponent extends React.Component {
updateOnSortingPages={this.updateOnSortingPages}
apps={apps}
setEditorMarginLeft={this.handleEditorMarginLeftChange}
slug={slug}
handleSlugChange={this.handleSlugChange}
/>
{!this.props.showComments && (

View file

@ -398,7 +398,7 @@ const EditorComponent = (props) => {
threshold: 0,
},
});
updateState({ appId: props?.params?.id });
getCanvasWidth();
initEditorWalkThrough();
};
@ -908,7 +908,7 @@ const EditorComponent = (props) => {
//! The computeComponentPropertyDiff function manages the calculation of differences in table columns by requiring complete column data. Without this complete data, the resulting JSON structure may be incorrect.
const paramDiff = computeComponentPropertyDiff(appDefinitionDiff, appDefinition, appDiffOptions);
const updateDiff = computeAppDiff(paramDiff, currentPageId, appDiffOptions, currentLayout);
console.log('----arpit::: appid ==> ', { appId });
updateAppVersion(appId, editingVersion?.id, currentPageId, updateDiff, isUserSwitchedVersion)
.then(() => {
const _editingVersion = {

View file

@ -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 { appsService } from '@/_services';
import { handleHttpErrorMessages, 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', false, true)?.errorMsg) {
setName(appName);
clearError();
setIsEditing(false);
return;
}
if (trimmedName === appName) {
setIsValid(true);
setIsEditing(false);
setName(appName);
return;
}
try {
await appsService.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 (
<ToolTip message={name} placement="bottom">
<div className={`app-name input-icon ${darkMode ? 'dark' : ''}`}>
<div className={`app-name input-icon ${darkMode ? 'dark' : ''}`}>
<ToolTip message={name} placement="bottom" isVisible={!isEditing}>
<input
ref={inputRef}
type="text"
onChange={(e) => {
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"
/>
</div>
</ToolTip>
</ToolTip>
<InfoOrErrorBox
active={isError || isEditing}
message={
errorMessage ||
warningText ||
(name.length >= 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' : ''}
/>
</div>
);
}

View file

@ -1,13 +1,15 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import cx from 'classnames';
import { SketchPicker } from 'react-color';
import { Confirm } from '../Viewer/Confirm';
import { HeaderSection } from '@/_ui/LeftSidebar';
import FxButton from '../CodeBuilder/Elements/FxButton';
import { CodeHinter } from '../CodeBuilder/CodeHinter';
import { resolveReferences } from '@/_helpers/utils';
import { resolveReferences, validateName, getWorkspaceId } from '@/_helpers/utils';
import { useTranslation } from 'react-i18next';
import _ from 'lodash';
import { appsService } from '@/_services';
import { replaceEditorURL, getHostURL } from '@/_helpers/routes';
import ExportAppModal from '../../HomePage/ExportAppModal';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { shallow } from 'zustand/shallow';
@ -22,6 +24,8 @@ export const GlobalSettings = ({
isMaintenanceOn,
backgroundFxQuery,
realState,
handleSlugChange,
slug: oldSlug,
}) => {
const { t } = useTranslation();
const { hideHeader, canvasMaxWidth, canvasMaxWidthType, canvasBackgroundColor } = globalSettings;
@ -29,6 +33,10 @@ export const GlobalSettings = ({
const [forceCodeBox, setForceCodeBox] = useState(true);
const [showConfirmation, setConfirmationShow] = useState(false);
const [isExportingApp, setIsExportingApp] = React.useState(false);
/* Unique app slug states */
const [slug, setSlug] = useState({ value: null, error: '' });
const [slugProgress, setSlugProgress] = useState(false);
const [isSlugUpdated, setSlugUpdatedState] = useState(false);
const { isVersionReleased } = useAppVersionStore(
(state) => ({
isVersionReleased: state.isVersionReleased,
@ -46,6 +54,59 @@ export const GlobalSettings = ({
left: '0px',
};
useEffect(() => {
/*
Only will fail for existed apps before the app/workspace url revamp which has
special chars or spaces in their app slugs
*/
const existedSlugErrors = validateName(oldSlug, 'App slug', true, false, false, false);
setSlug({ value: oldSlug, error: existedSlugErrors.errorMsg });
}, [oldSlug]);
const handleInputChange = (value, field) => {
setSlug({
value: slug?.value,
error: null,
});
const error = validateName(value, `App ${field}`, true, false, !(field === 'slug'), !(field === 'slug'));
if (!_.isEmpty(value) && value !== oldSlug && _.isEmpty(error.errorMsg)) {
setSlugProgress(true);
appsService
.setSlug(app?.id, value)
.then(() => {
setSlug({
value,
error: '',
});
setSlugProgress(false);
handleSlugChange(value);
setSlugUpdatedState(true);
replaceEditorURL(value, realState?.page?.handle);
})
.catch(({ error }) => {
setSlug({
value,
error,
});
setSlugProgress(false);
setSlugUpdatedState(false);
});
} else {
setSlugProgress(false);
setSlugUpdatedState(false);
setSlug({
value,
error: error?.errorMsg,
});
}
};
const delayedSlugChange = _.debounce((value, field) => {
handleInputChange(value, field);
}, 500);
const outerStyles = {
width: '142px',
height: '32px',
@ -84,12 +145,72 @@ export const GlobalSettings = ({
darkMode={darkMode}
/>
)}
<div id="" className={cx({ 'dark-theme': darkMode, disabled: isVersionReleased })}>
<div id="" className={cx({ 'dark-theme': darkMode })}>
<div bsPrefix="global-settings-popover">
<HeaderSection darkMode={darkMode}>
<HeaderSection.PanelHeader title="Global settings" />
</HeaderSection>
<div style={{ padding: '12px 16px' }}>
<div className="card-body">
<div className="app-slug-container">
<div className="row">
<div className="col tj-app-input input-with-icon">
<label className="field-name">Unique app slug</label>
<input
type="text"
className={`form-control ${slug?.error ? 'is-invalid' : 'is-valid'} slug-input`}
placeholder={t('editor.appSlug', 'Unique app slug')}
maxLength={50}
onChange={(e) => {
e.persist();
delayedSlugChange(e.target.value, 'slug');
}}
data-cy="app-slug-input-field"
defaultValue={oldSlug}
/>
{isSlugUpdated && (
<div className="icon-container">
<svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.256 0.244078C14.5814 0.569515 14.5814 1.09715 14.256 1.42259L5.92263 9.75592C5.59719 10.0814 5.06956 10.0814 4.74412 9.75592L0.577452 5.58926C0.252015 5.26382 0.252015 4.73618 0.577452 4.41074C0.902889 4.08531 1.43053 4.08531 1.75596 4.41074L5.33337 7.98816L13.0775 0.244078C13.4029 -0.0813592 13.9305 -0.0813592 14.256 0.244078Z"
fill="#46A758"
/>
</svg>
</div>
)}
{slug?.error ? (
<label className="label tj-input-error">{slug?.error || ''}</label>
) : isSlugUpdated ? (
<label className="label label-success">{`Slug accepted!`}</label>
) : (
<label className="label label-info">{`URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens`}</label>
)}
</div>
</div>
<div className="row">
<div className="col modal-main tj-app-input">
<label className="field-name">App link</label>
<div className={`tj-text-input break-all ${darkMode ? 'dark' : ''}`}>
{!slugProgress ? (
`${getHostURL()}/${getWorkspaceId()}/apps/${slug?.value || oldSlug || ''}`
) : (
<div className="d-flex gap-2">
<div class="spinner-border text-secondary workspace-spinner" role="status">
<span class="visually-hidden">Loading...</span>
</div>
{`Updating link`}
</div>
)}
</div>
<label className="label label-success label-updated">
{isSlugUpdated ? `Link updated successfully!` : ''}
</label>
</div>
</div>
</div>
</div>
<div style={{ padding: '12px 16px' }} className={cx({ disabled: isVersionReleased })}>
<div className="tj-text-xsm color-slate12 ">
<div className="d-flex mb-3">
<span data-cy={`label-hide-header-for-launched-apps`}>

View file

@ -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 (
<div className={additionalClassName} style={boxStyle}>
{message && <div>{message}</div>}
</div>
);
}
export default InfoOrErrorBox;

View file

@ -12,9 +12,11 @@ import config from 'config';
// eslint-disable-next-line import/no-unresolved
import { useUpdatePresence } from '@y-presence/react';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { useCurrentState } from '@/_stores/currentStateStore';
import { shallow } from 'zustand/shallow';
import { useAppDataActions, useAppInfo, useCurrentUser } from '@/_stores/appDataStore';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { redirectToDashboard } from '@/_helpers/routes';
export default function EditorHeader({
M,
@ -34,11 +36,7 @@ export default function EditorHeader({
}) {
const currentUser = useCurrentUser();
const { updateState } = useAppDataActions();
const { isSaving, appId, appName, app, isPublic } = useAppInfo();
const handleSlugChange = (newSlug) => {
updateState({ slug: newSlug });
};
const { isVersionReleased, editingVersion } = useAppVersionStore(
(state) => ({
@ -47,6 +45,7 @@ export default function EditorHeader({
}),
shallow
);
const currentState = useCurrentState();
const updatePresence = useUpdatePresence();
@ -67,7 +66,7 @@ export default function EditorHeader({
const handleLogoClick = (e) => {
e.preventDefault();
// Force a reload for clearing interval triggers
window.location.href = '/';
redirectToDashboard();
};
return (
@ -155,9 +154,9 @@ export default function EditorHeader({
app={app}
appId={appId}
slug={slug}
M={M}
handleSlugChange={handleSlugChange}
darkMode={darkMode}
pageHandle={currentState?.page?.handle}
M={M}
isPublic={isPublic ?? false}
/>
)}

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