Merge branch 'main' of https://github.com/ToolJet/ToolJet into appbuilder-1.6

This commit is contained in:
Kavin Venkatachalam 2024-03-04 11:36:13 +05:30
commit b5ab470d09
311 changed files with 113506 additions and 3230 deletions

View file

@ -115,7 +115,7 @@ jobs:
- name: Send Slack Notification
run: |
if [[ "${{ job.status }}" == "success" ]]; then
message="Job '${{ env.JOB_NAME }}' succeeded! tooljet/tooljet-ce:${{ github.event.inputs.image }}"
message="ToolJet community image published:\n\`tooljet/tooljet-ce:${{ github.event.inputs.image }}\`"
else
message="Job '${{ env.JOB_NAME }}' failed! tooljet/tooljet-ce:${{ github.event.inputs.image }}"
fi

View file

@ -1 +1 @@
2.30.4
2.32.2

View file

@ -115,9 +115,9 @@ Cypress.Commands.add(
.last()
.click()
.type(createBackspaceText(text), { delay: 0 }),
{
delay: 0,
};
{
delay: 0,
};
});
if (!Array.isArray(value)) {
cy.wrap(subject).last().type(value, {
@ -193,9 +193,9 @@ Cypress.Commands.add(
.invoke("text")
.then((text) => {
cy.wrap(subject).realType(createBackspaceText(text)),
{
delay: 0,
};
{
delay: 0,
};
});
}
);
@ -273,7 +273,7 @@ Cypress.Commands.add("waitForAppLoad", () => {
const API_ENDPOINT =
Cypress.env("environment") === "Community"
? "/api/v2/data_sources"
: "/api/app-environments/**";
: "/api/app-environments**";
const TIMEOUT = 15000;
@ -460,3 +460,11 @@ Cypress.Commands.add("skipWalkthrough", () => {
win.localStorage.setItem("walkthroughCompleted", "true");
});
});
Cypress.Commands.add("appPrivacy", (appName, isPublic) => {
const isPublicValue = isPublic ? "true" : "false";
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `UPDATE apps SET is_public = ${isPublicValue} WHERE name = '${appName}';`,
});
});

View file

@ -10,7 +10,6 @@ import {
} from "Support/utils/workspaceConstants";
import { buttonText } from "Texts/button";
import {
verifyAndModifyParameter,
editAndVerifyWidgetName,
} from "Support/utils/commonWidget";
import { verifypreview } from "Support/utils/dataSource";
@ -276,10 +275,12 @@ describe("Workspace constants", () => {
verifypreview("raw", "dJ_8Q~BcaMPd");
cy.dragAndDropWidget("Text", 550, 350);
editAndVerifyWidgetName(data.constantsName);
editAndVerifyWidgetName(data.constantsName, []);
cy.waitForAutoSave();
verifyAndModifyParameter("Text", `{{constants.${data.constantsName}`);
cy.get(
'[data-cy="textcomponenttextinput-input-field"]'
).clearAndTypeOnCodeMirror(`{{constants.${data.constantsName}`);
cy.forceClickOnCanvas();
cy.waitForAutoSave();

View file

@ -95,16 +95,15 @@ describe("Editor- Global Settings", () => {
cy.forceClickOnCanvas();
cy.wait(500);
cy.waitForAutoSave();
cy.get('[data-cy="button-release"]').click();
cy.get('[data-cy="yes-button"]').click();
cy.get('[data-cy="editor-page-logo"]').click();
cy.get('[data-cy="back-to-app-option"]').click();
cy.get(`[data-cy="${data.appName.toLowerCase()}-card"]`)
.realHover()
.find('[data-cy="launch-button"]')
.invoke("attr", "class")
.should("contains", "disabled-btn");
//Fix this after the release. 2.9.0
// cy.get('[data-cy="button-release"]').click();
// cy.get('[data-cy="yes-button"]').click();
// cy.get('[data-cy="editor-page-logo"]').click();
// cy.get(`[data-cy="${data.appName.toLowerCase()}-card"]`)
// .realHover()
// .find('[data-cy="launch-button"]')
// .invoke("attr", "class")
// .should("contains", "disabled-btn");
cy.apiDeleteApp();
});

View file

@ -100,7 +100,7 @@ describe("Chaining of queries", () => {
.find("input")
.type(`{selectAll}{backspace}psql{enter}`);
cy.forceClickOnCanvas();
cy.wait(2500)
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "psql");
cy.verifyToastMessage(commonSelectors.toastMessage, "runjs");
@ -140,7 +140,7 @@ describe("Chaining of queries", () => {
.find("input")
.type(`{selectAll}{backspace}runjs{enter}`);
cy.forceClickOnCanvas();
cy.wait(2500)
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "runjs");
cy.verifyToastMessage(commonSelectors.toastMessage, "runpy");

View file

@ -35,7 +35,7 @@ import {
describe("Editor- Test Button widget", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.apiCreateApp(`${fake.companyName}-button-App`);
cy.openApp();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 500);
});
@ -352,7 +352,7 @@ describe("Editor- Test Button widget", () => {
cy.wait(500);
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
cy.get('[data-cy="input-textinput1"]').should(
cy.get(commonWidgetSelector.draggableWidget('textinput1')).should(
"have.value",
data.customMessage
);
@ -452,6 +452,7 @@ describe("Editor- Test Button widget", () => {
cy.apiDeleteApp();
});
it("Should verify deletion of button component from right side panel", () => {
openEditorSidebar(buttonText.defaultWidgetName);
cy.get('[data-cy="component-inspector-options"]').click();
cy.get('[data-cy="component-inspector-delete-button"]').click();
cy.get('[data-cy="yes-button"]').click();

View file

@ -417,7 +417,7 @@ describe("Basic components", () => {
cy.resizeWidget("form1", 650, 400, false);
openEditorSidebar("form1");
editAndVerifyWidgetName("form2");
editAndVerifyWidgetName("form2", []);
cy.waitForAutoSave();

View file

@ -93,6 +93,7 @@ describe("List view widget", () => {
)
.realHover()
.realClick();
openEditorSidebar(commonWidgetText.text1);
cy.get(
'[data-cy="textcomponenttextinput-input-field"] '
).clearAndTypeOnCodeMirror(codeMirrorInputLabel("listItem.name"));

View file

@ -314,7 +314,7 @@ describe("Table", () => {
cy.get('[data-cy="inspector-close-icon"]').click();
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionLayout, []);
openAccordion('Layout', []);
verifyAndModifyToggleFx(
"Show on desktop",
@ -885,6 +885,7 @@ describe("Table", () => {
});
it("should verify download", () => {
deleteDownloadsFolder();
cy.get(tableSelector.buttonDownloadDropdown).should("be.visible").click();
cy.get(tableSelector.optionDownloadPdf).click();
cy.task("readPdf", "cypress/downloads/all-data.pdf")
@ -1139,7 +1140,7 @@ describe("Table", () => {
cy.get(".tooltip-inner").invoke("hide");
verifyNodeData("components", "Object", "1 entry ");
openNode("components");
verifyNodeData(tableText.defaultWidgetName, "Object", "22 entries ");
verifyNodeData(tableText.defaultWidgetName, "Object", "26 entries ");
cy.wait(1000);
openNode(tableText.defaultWidgetName, 0, 1);
// openNode(tableText.defaultWidgetName, 0, 1);
@ -1246,7 +1247,7 @@ describe("Table", () => {
'[data-cy="textcomponenttextinput-input-field"]'
).clearAndTypeOnCodeMirror("Column Email");
// verifyAndModifyParameter("Text", "Column Email");
cy.get('[data-cy="inspector-close-icon"]').click();
cy.get('[data-cy="inspector-close-icon"]').click({ force: true });
cy.get(`[data-cy="draggable-widget-${commonWidgetText.text1}"]`).should(
"have.text",
"Column Email"

View file

@ -32,7 +32,6 @@ describe("Bulk user upload", () => {
it("Verfiy bulk user upload invalid files", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase();
data.workspaceName = data.firstName.toLowerCase();
cy.apiLogin()
@ -121,7 +120,6 @@ describe("Bulk user upload", () => {
it("Verify bulk user upload functionality", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase();
data.workspaceName = data.firstName.toLowerCase();
cy.apiLogin()

View file

@ -165,7 +165,7 @@ describe("dashboard", () => {
verifyTooltip(commonSelectors.databaseIcon, "ToolJet Database");
verifyTooltip(commonSelectors.globalDataSourceIcon, "Data sources");
verifyTooltip(
'[data-cy="icon-workspace-constants"]',
commonSelectors.workspaceConstantsIcon,
"Workspace constants"
);
verifyTooltip(commonSelectors.notificationsIcon, "Comment notifications");

View file

@ -324,7 +324,7 @@ describe("Manage Users", () => {
"have.text",
"User groups"
);
cy.get(".css-3w2yfm-ValueContainer").should("be.visible");
cy.get('[data-cy="user-group-select"]>>>>>').should("be.visible");
cy.get(commonSelectors.cancelButton).verifyVisibleElement(
"have.text",
"Cancel"
@ -333,16 +333,19 @@ describe("Manage Users", () => {
"have.text",
"Update"
);
cy.get(".css-3w2yfm-ValueContainer").click();
cy.get(".css-1c6ox7i-Input").type("Admin");
cy.get(".form-check-input").check();
cy.get('[data-cy="user-group-select"]>>>>>').eq(0).type("Admin");
cy.wait(1000);
cy.get('[data-cy="group-check-input"]').eq(0).check();
cy.get(commonSelectors.cancelButton).click();
cy.get(usersSelector.userActionButton).click();
cy.get(usersSelector.editUserDetailsButton).click();
cy.get(".css-3w2yfm-ValueContainer").click();
cy.get(".css-1c6ox7i-Input").type("Admin");
cy.get(".form-check-input").check();
cy.get('[data-cy="user-group-select"]>>>>>').eq(0).type("Admin");
cy.wait(1000);
cy.get('[data-cy="group-check-input"]').eq(0).check();
cy.get(usersSelector.buttonInviteUsers).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,

View file

@ -106,7 +106,7 @@ export const verifyBasicData = (widgetName, data) => {
force: true,
});
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
cy.get(`[data-cy='input-textinput1']`).should(
cy.get(`[data-cy="draggable-widget-textinput1"]`).should(
"have.value",
data.customMessage
);

View file

@ -45,7 +45,6 @@ export const verifyCSA = (data) => {
force: true,
});
cy.get(commonWidgetSelector.draggableWidget("textinput1"))
.find("input")
.clear()
.type(data.customText);
cy.get(

View file

@ -12,10 +12,13 @@ export const selectEvent = (
.click()
.find("input")
.type(`{selectAll}{backspace}${event}{enter}`);
cy.get('[data-cy="event-label"]').click({force:true})
cy.get('[data-cy="action-selection"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${action}{enter}`);
cy.get('[data-cy="event-label"]').click({force:true})
cy.wait("@events");
};
@ -29,14 +32,19 @@ export const selectCSA = (
.click()
.find("input")
.type(`{selectAll}{backspace}${component}{enter}`);
cy.get('[data-cy="event-label"]').click({force:true})
cy.get('[data-cy="action-options-action-selection-field"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${componentAction}{enter}`);
cy.get('[data-cy="event-label"]').click({force:true})
cy.wait("@events");
cy.get('[data-cy="debounce-input-field"]')
.click()
.type(`{selectAll}{backspace}${debounce}{enter}`);
cy.get('[data-cy="event-label"]').click({force:true})
cy.wait("@events");
};
@ -45,6 +53,7 @@ export const addSupportCSAData = (field, data) => {
cy.get(`[data-cy="event-${field}-input-field"]`)
.click({ force: true })
.clearAndTypeOnCodeMirror(data);
cy.get('[data-cy="event-label"]').click({force:true})
};
export const selectSupportCSAData = (option) => {
@ -54,6 +63,7 @@ export const selectSupportCSAData = (option) => {
.click()
.find("input")
.type(`{selectAll}{backspace}${option}{enter}`);
cy.get('[data-cy="event-label"]').click({force:true})
cy.wait("@events");
};
@ -64,5 +74,6 @@ export const changeEventType = (event, eventIndex = 0) => {
.click()
.find("input")
.type(`{selectAll}{backspace}${event}{enter}`);
cy.get('[data-cy="event-label"]').click({force:true})
cy.wait("@events");
};

View file

@ -95,7 +95,7 @@ export const verifyValuesOnList = (
cy.get(commonWidgetSelector.draggableWidget(childName)).each(
($element, i) => {
if (isChild) {
cy.wrap($element).find("input").should(`have.${type}`, value[i]);
cy.wrap($element).should(`have.${type}`, value[i]);
} else {
cy.wrap($element).should(`have.${type}`, value[i]);
}

View file

@ -264,17 +264,15 @@ export const fillUserInviteForm = (firstName, email) => {
export const selectUserGroup = (groupName) => {
cy.wait(1500);
cy.get("body").then(($body) => {
const selectDropdown = $body.find('[data-cy="user-group-select"]>>>>>');
if (selectDropdown.length === 0) {
cy.get('[data-cy="user-group-select"]>>>>>').click();
}
cy.get('[data-cy="user-group-select"]>>>>>').eq(0).type(groupName);
cy.wait(1000);
cy.get('[data-cy="user-group-select"]>>>>>').eq(2).click();
cy.get('[data-cy="group-check-input"]').eq(0).check()
});
};
@ -296,11 +294,11 @@ export const inviteUserWithUserGroups = (
}
cy.get('[data-cy="user-group-select"]>>>>>').eq(0).type(groupName1);
cy.wait(1000);
cy.get('[data-cy="user-group-select"]>>>>>').eq(2).click();
cy.get('[data-cy="group-check-input"]').eq(0).check()
cy.wait(1000);
cy.get('[data-cy="user-group-select"]>>>>>').eq(0).type(groupName2);
cy.wait(1000);
cy.get('[data-cy="user-group-select"]>>>>>').eq(4).click();
cy.get('[data-cy="group-check-input"]').eq(0).check()
});
cy.get(usersSelector.buttonInviteUsers).click();

View file

@ -9,7 +9,7 @@ A Super Admin is the user who has full access to all the Workspaces, Users, and
The user details entered while setting up ToolJet will have Super Admin privileges.
## How is Super Admin different from Admin
## How Super Admin is different from Admin
| Privilege | Admin | Super Admin |
| --------- | ----- | ----------- |
@ -24,34 +24,30 @@ The user details entered while setting up ToolJet will have Super Admin privileg
| [Access any user's ToolJet database (create, edit or delete database)](#access-tooljet-db-in-any-workspace) | ❌ | ✅ |
| [Manage any workspace's setting (Groups/SSO/Workspace constants)](#manage-workspace-setting-groupsssoworkspace-constants) | ❌ | ✅ |
| [Manage all users from all the workspaces in the instance](#manage-all-users-in-the-instance) | ❌ | ✅ |
| [Archive/Unarchive any user from all the workspaces in the instance](#archiving-a-user-from-all-the-workspaces-instance-level) | ❌ | ✅ |
| [Reset password of any user](#reset-password-of-any-user) | ❌ | ✅ |
| [Edit name of any user](#edit-name) | ❌ | ✅ |
| [Make any user Super Admin](#make-the-user-super-admin) | ❌ | ✅ |
| [Manage all workspaces in the instance(Archive/Unarchive)](#all-workspaces) | ❌ | ✅ |
| [Restrict creation of personal workspace of users](#restrict-creation-of-personal-workspace-of-users) | ❌ | ✅ |
| [Enable Multiplayer editing](#enable-multiplayer-editing) | ❌ | ✅ |
| [Implement White Labelling](#white-labelling) | ❌ | ✅ |
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/superadmin/instanceset.png" alt="Super Admin: Enterprise" />
</div>
## Super Admin features
### Access any workspace
If a user has Super Admin privileges, they can switch to any workspace created by any user within the instance using the Workspace Switcher located in the bottom left corner of the screen.
If a user is a Super Admin, they can switch to any workspace created by any user within the instance using the Workspace Switcher located in the bottom left corner of the screen.
The dropdown will display all workspaces, including those created by both Super Admins and any other users.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/superadmin/workspaceswitcher.png" alt="Super Admin: Enterprise" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/workspaceswitcher.png" alt="Superadmin: settings" />
</div>
### Create, Edit or Delete apps from any user's personal workspace
Once the Super Admin accesses the workspace of any other user, they can create, edit or delete app on the workspace.
Once the Super Admin access the workspace of any other user, they can create, edit or delete app on the workspace.
This also includes - modifying folders and importing, exporting, or cloning apps to any user's workspace.
@ -62,9 +58,7 @@ Super Admin can not only archive/unarchive users/admins on their workspace but a
If a user is Super Admin, they just need to open the workspace in which they want to archive or unarchive a user. Then go to the **Workspace Settings** from the sidebar -> **Manage Users** -> **Archive/Unarchive** any user/admin
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/superadmin/unarchivesa.png" alt="Super Admin: Enterprise" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/archiveusersa.png" alt="Superadmin: settings" />
</div>
### Access ToolJet DB in any workspace
@ -83,57 +77,106 @@ Super Admins have all the privileges that an Admin of a workspace have, Super Ad
## Settings
Only Super Admins can access the Settings:
Only Super Admins can access the Settings. To access the Settings page, click on the **⚙️** button and select **Settings** from the dropdown.
- **All Users**
- **Manage Settings**
- **License**
- **White labelling**
- **[All Users](#all-users)**
- **[All workspaces](#all-workspaces)**
- **[Manage instance settings](#manage-instance-settings)**
- **[License](#license)**
- **[White labelling](#white-labelling)**
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/settings.png" alt="Superadmin: settings" />
</div>
## All Users
### Manage all users in the instance
**All Users** page can be used to check the list of all the users in the instance. Super Admins can also promote/demote any user to/from Super Admin from this page. They can also archive/unarchive any user from this page.
**All Users** settings can be used to check the list of all the users available on all the workspaces in the instance. Super Admins can also promote/demote any user to/from Super Admin from this page. They can also archive/unarchive any user at an instance level from this setting.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/superadmin/allusersa.png" alt="Super Admin: Enterprise" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/allusers1.png" alt="Superadmin: settings" />
</div>
### Archiving a user from workspace
### Archiving a user from all the workspaces (Instance level)
Super Admins have the privilege to remove any user from any workspace to which they belong.
Super Admins have the authority to deactivate any user at instance level. This will remove the user from all the workspaces in the instance.
Super Admins can go to **All Users** page, Under the **Workspaces** column they'll see the number of workspaces a user belongs to. Click on the **`View(n)`**, a modal will pop up that will have the list of **`n`** number the workspaces, click on the **Archive/Unarchive** button next to the workspace name to remove the user from the workspace.
To archive a user, go to the **All Users** settings, click on the kebab menu next to the user that is to be archived and select **Archive** option. Once the user is archived, the status will change from **Active** to **Archived**. The user will not be able to login to any workspace in the instance.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/superadmin/archivesa.png" alt="Super Admin: Enterprise" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/archiveinstance.png" alt="Superadmin: settings" />
</div>
### Make the user super admin
<br/>
Super Admins can make any user as Super Admin or remove any Super Admin from the **Manage All Users** in the Settings page.
**Unarchiving** a user from **All Users** settings will unarchive the user from the instance and not at workspace level.
Click on the **Edit** button next to any user, **Enable** the **Make the user Super Admin** option, and then **Save** it.
The user will become Super Admin and the Type column will update from **`workspace`** to **`instance`**.
**Info**: The user will be unarchived from instance level automatically if a workspace admin unarchives the user from their workspace.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/enterprise/superadmin/saset.png" alt="Super Admin: Enterprise" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/unarchiveinstance.png" alt="Superadmin: settings" />
</div>
## Manage Settings
### Reset password of any user
Super Admins can reset the password of any user from the **All Users** settings. To reset the password, click on the kebab menu next to the user and select **Reset Password** option. A pop-up will appear asking either to auto-generate a password or to enter a new password.
### Edit user details
Super Admins can edit the details of any user from the **All Users** settings. To edit the details, click on the kebab menu next to the user and select **Edit user details** option.
#### Edit name
On selecting the **Edit user details** option, a drawer will open from the right. Super Admins can edit the name of the user from this drawer. Once the changes are made, click on the **Update** button.
#### Make the user Super Admin
From the **Edit user details** drawer, Super Admins can make any user as Super Admin or remove any Super Admin from the **All Users** settings. To make a user Super Admin, toggle on the **Super Admin** radio button. The user will become Super Admin and the Type column will update from **`Workspace`** to **`Instance`**.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/edituserdetailsinstance.png" alt="Superadmin: settings" />
</div>
<img className="screenshot-full" src="/img/enterprise/superadmin/instancesett.png" alt="Super Admin: Enterprise" />
## All workspaces
The All Workspaces tab provides a comprehensive view of all workspaces within the ToolJet instance. Super Admins can use this functionality to monitor and manage workspaces collectively, ensuring efficient administration and organization-wide oversight.
Super Admins have the authority to **archive** or **unarchive** workspaces of any user in the instance as needed. Archiving a workspace essentially sets it to an inactive state, removing it from active use. Conversely, unarchiving reactivates a previously archived workspace, making it accessible once again.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/workspaces1.png" alt="Superadmin: settings" />
</div>
### Current Workspace
The **Current Workspace** label will be displayed next to the workspace that the Super Admin has currently opened. If the Super Admin archives the current workspace, they will be prompted to switch to another active workspace to ensure continuous accessibility.
### Open Active Workspaces
In the list of active workspaces, there is an option to open the workspace directly. This feature helps superadmins to quickly navigate to the workspace on the new tab of the browser and manage the workspace.
### Archive Workspaces
The **Archive** button on the right of the workspace name allows Super Admins to archive the workspace. Once archived, the workspace will be moved to the **Archived Workspaces** section.
**Impact**:
- The apps on the archived workspace won't be accessable through the URL
- Users will be logged out if they don't have access to any active workspace
### Archived Workspaces
The **Archived** section displays a list of all archived workspaces. Super Admins can unarchive any workspace from this section by clicking the **Unarchive** button.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/workspaces2.png" alt="Superadmin: settings" />
</div>
## Manage instance settings
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/enterprise/superadmin/instanceoptions.png" alt="Superadmin: settings" />
</div>
### Restrict creation of personal workspace of users
@ -146,13 +189,17 @@ Super Admins can **control** this behavior from the Manage Settings page, they c
Super Admins can enable multiplayer editing from the Manage Settings page. Once enabled, users will be able to edit the same app simultaneously resulting in real-time collaboration.
## License
### Comments
Manage the instance license via the **Settings** page. Super Admins have the capability to update the instance's license key from this page.
Check out the [License](/docs/licensing) page for more details.
Super Admins can enable comments from the Manage Settings page. Once enabled, users will be able to collaborate by adding comments anywhere on the canvas.
## White labelling
This feature allows you to customize the ToolJet instance with your own branding. You can change the logo, favicon, and the name of the instance.
Check out the [White labelling](/docs/enterprise/white-label/) page for more details.
Check out the [White labelling](/docs/enterprise/white-label/) page for more details.
## License
Manage the instance license via the **Settings** page. Super Admins have the capability to update the instance's license key from this page.
Check out the [License](/docs/licensing) page for more details.

View file

@ -1,85 +1,62 @@
---
id: access-users-location
title: Access a user's location
title: Accessing User Location with RunJS Query (Geolocation API)
---
# Access a user's location using RunJS query (Geolocation API)
In this how-to guide, we will build a ToolJet application that will utilize the **JavaScript Geolocation API** to get the user's location. The Geolocation API provides access to geographical location data associated with a user's device. This can be determined using GPS, WIFI, IP Geolocation and so on.
In this step-by-step guide we will build a ToolJet application that harnesses the power of the **JavaScript Geolocation API** to retrieve the user's location. The Geolocation API offers access to various geographical data associated with a user's device, utilizing methods such as GPS, WIFI, IP Geolocation, and more.
:::info
To protect the user's privacy, Geolocation API requests permission to locate the device. If the user grants permission, you will gain access to location data such as latitude, longitude, altitude, and speed.
To uphold user privacy, the Geolocation API requests permission before locating the device. Upon permission, you gain access to data like latitude, longitude, altitude, and speed.
:::
- Let's start by creating a new application
1. Begin by creating a new application:
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/access-location/newapp.png" alt="How to: Access User's Location" />
</div>
<div style={{textAlign: 'center'}}>
2. In the app editor, navigate to the query panel at the bottom and create a **[RunJS query](/docs/data-sources/run-js/#runjs-query-examples)** by selecting **Run JavaScript Code** as the datasource:
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/access-location/runjsq.png" alt="How to: Access User's Location" />
</div>
<img className="screenshot-full" src="/img/how-to/access-location/newapp.png" alt="New App" />
</div>
- In the app editor, go to the query panel at the bottom and create a **[RunJS query](/docs/data-sources/run-js/#runjs-query-examples)** by selecting **Run JavaScript Code** as the datasource
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/access-location/runjs.png" alt="New App" />
</div>
- You can use the following javascript code that makes use of geolocation api to get the location
```js
function getCoordinates() {
return new Promise(function(resolve, reject) {
navigator.geolocation.getCurrentPosition(resolve, reject);
3. Utilize the following JavaScript code to employ the Geolocation API and retrieve the location:
```js
function getCoordinates() { // Function to get coordinates
return new Promise(function (resolve, reject) { // Promise to get coordinates
navigator.geolocation.getCurrentPosition(resolve, reject); // Get current position
});
}
}
async function getAddress() { // Function to get address
const position = await getCoordinates(); // Await the coordinates
let latitude = position.coords.latitude; // Get latitude
let longitude = position.coords.longitude; // Get longitude
return [latitude, longitude]; // Return the coordinates
}
return await getAddress(); // Return the address
```
async function getAddress() {
// notice, no then(), cause await would block and
// wait for the resolved result
const position = await getCoordinates();
let latitude = position.coords.latitude;
let longitude = position.coords.longitude;
4. Scroll down the query editor and from **Settings** enable the `Run this query on application load?` option. This ensures that the JavaScript query runs each time the app is opened, providing the user's location.
return [latitude, longitude];
}
5. Upon clicking **Run**, your browser prompts you to grant permission for the ToolJet app to access your location. Allow this permission to receive location data.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/access-location/newprompt.png" alt="How to: Access User's Location" />
</div>
return await getAddress()
```
7. Once the query is succesfully run, the coordinates will be returned and displayed in the **Preview** section of query editor. To inspect the data returned by the query, go to the **Inspector** on the left sidebar, expand queries -> `runjs1` (query name), and then examine the **data**. You'll find the coordinates.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/access-location/newdata.png" alt="How to: Access User's Location" />
</div>
- Now, go to the **Advanced** tab and enable the `Run query on page load?` option. Enabling this option will run this javascript query every time the app is opened by the user and the query will return the location
- **Save** the query and hit the **Run** button
- As soon as you hit the **Run** button, the browser will prompt you to allow the permission to share the location access to ToolJet app. You'll need to **allow** it to return the location data
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/access-location/prompt.png" alt="New App" />
</div>
- Now, to check the data returned by the query go to the **Inspector** on the left sidebar. Expand the queries -> `runjs1`(query name) -> and then expand the **data**. You'll find the coordinates
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/access-location/data.png" alt="New App" />
</div>
- Next, we can use these coordinates returned by the query on the **map component** to show the location. Drop a map component on the canvas and edit its properties. In the **Initial location** property, enter
```js
{{ {"lat": queries.runjs1.data[0], "lng": queries.runjs1.data[1]} }}
```
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/access-location/map.png" alt="New App" />
</div>
- Finally, you'll see the location updated on the **map component**
8. Utilize these coordinates in the **map component** to display the location. Add a map component to the canvas and edit its properties. In the **Initial location** property, enter:
```js
{{ {"lat": queries.runjs1.data[0], "lng": queries.runjs1.data[1]} }}
```
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/access-location/newmap.png" alt="How to: Access User's Location" />
</div>
9. Once the Map component properties are updated, you'll see the location displayed on the **map component**.

View file

@ -3,94 +3,116 @@ id: import-external-libraries-using-runjs
title: Import external libraries using RunJS
---
ToolJet allows you to utilize external libraries in your app by importing them using the [RunJS query](/docs/data-sources/run-js).
ToolJet allows you to integrate external JavaScript libraries into your application using RunJS queries. This guide walks you through the process of importing and utilizing these libraries effectively.
In this how-to guide, we will import a few JavaScript libraries and use it in the application.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
:::tip
You can import any of the available libraries using their **CDN**. Find free CDN of the open source projects at **[jsDelivr](https://www.jsdelivr.com/)**
:::
## Choosing Libraries
- Create a new application and then create a new RunJS query from the query panel.
<div style={{textAlign: 'center'}}>
You can import various JavaScript libraries using their Content Delivery Network (CDN) links. Find the CDN links for your desired open-source projects on [jsDelivr](https://www.jsdelivr.com/).
<img className="screenshot-full" src="/img/how-to/import-js/newquery.png" alt="Import external libraries using RunJS" />
## Creating a new app and runJS query
</div>
Start by creating a new application in ToolJet. Then, proceed to create a new RunJS query from the query panel.
- Let's write some code for importing libraries. We will first create a function `addScript` that returns a `Promise`, the `Promise` creates a script tag -> sets an attribute -> and eventListener `resolves` if its loaded and `rejects` if there is an error, and then body is appended at the end.
- We are going to import two libraries using their CDNs: **MathJS** and **Flatten**, and display an alert when the libraries are loaded successfully.
```js
function addScript(src) {
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/import-js/neww.png" alt="reate a new RunJS query" />
</div>
</div>
## Importing Libraries
Here's a step-by-step guide to importing libraries and displaying an alert upon successful import.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
```js
// Function to add script dynamically
function addScript(src) {
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.setAttribute('src', src);
s.addEventListener('load', resolve);
s.addEventListener('error', reject);
document.body.appendChild(s);
const scriptTag = document.createElement('script');
scriptTag.setAttribute('src', src);
scriptTag.addEventListener('load', resolve);
scriptTag.addEventListener('error', reject);
document.body.appendChild(scriptTag);
});
}
}
try {
try {
// Importing MathJS
await addScript('https://cdn.jsdelivr.net/npm/mathjs@11.7.0');
// Importing FlattenJS
await addScript('https://cdn.jsdelivr.net/npm/flattenjs@2.1.3/lib/flatten.min.js');
await actions.showAlert("success", 'Mathjs and Flatten imported')
} catch (e) {
console.log(e);
}
```
// Showing a success alert
await actions.showAlert("success", 'Mathjs and Flatten imported');
} catch (error) {
console.error(error);
}
```
- Now, when you hit **create** and then **run** the query, the script will be injected into the DOM. An alert should pop-up with the message **Mathjs and Flatten imported**.
<div style={{textAlign: 'center'}}>
</div>
<img className="screenshot-full" src="/img/how-to/import-js/imported.png" alt="Import external libraries using RunJS"/>
After creating and running the query, an alert should pop up with the message "Mathjs and Flatten imported."
</div>
:::tip
Enable the **Run this query on application load?** option to make the libraries available throughout the application as soon as the app is loaded.
:::
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/import-js/importeds.png" alt="reate a new RunJS query" />
</div>
</div>
## Examples
### Flatten the JSON objects using FlattenJS
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
- Let's create a new **RunJS** query that will use **Flatten** library(imported in the above section) and the query will flatten the JSON object.
```js
return flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
```
- Save the query, you can either **Preview** the output on the query manager or **Run** the query to check the output on the inspector on the left-sidebar.
### 1. Flattening JSON Objects using FlattenJS
<div style={{textAlign: 'center'}}>
Create a new RunJS query using the Flatten library (imported earlier) to flatten a JSON object.
<img className="screenshot-full" src="/img/how-to/import-js/flatten.png" alt="Import external libraries using RunJS"/>
</div>
### Computation using MathJS
- Let's create a new **RunJS** query that will return the result of calculation performed by [atan2](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2) method and then divided by [pi](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/PI).
```js
return math.atan2(3, -3) / math.pi
return flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
});
```
- Save the query, you can either **Preview** the output on the query manager or **Run** the query to check the output on the inspector on the left-sidebar.
Preview the output in the query manager or run the query to see the flattened JSON.
<div style={{textAlign: 'center'}}>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/import-js/flattens.png" alt="reate a new RunJS query" />
</div>
<img className="screenshot-full" src="/img/how-to/import-js/mathjs.png" alt="Import external libraries using RunJS"/>
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### 2. Computation using MathJS
Create another RunJS query utilizing the MathJS library for a calculation.
```js
return math.atan2(3, -3) / math.pi;
```
Preview the output, or Run the query to see the result of the calculation.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/import-js/mathjss.png" alt="reate a new RunJS query" />
</div>
</div>
This guide provides a clear and detailed walkthrough for importing external JavaScript libraries into your ToolJet application.

View file

@ -1,23 +1,37 @@
---
id: intentionally-fail-js-query
title: Intentionally fail a RunJS query
title: Intentionally Throwing an Error in RunJS for Debugging
---
In this how-to guide, we will create a RunJS query that will throw an error.
In this step-by-step guide, we'll walk you through the process of creating a RunJS query that intentionally throws an error for debugging purposes.
- Create a RunJS query and paste the code below. We will use the constructor `ReferenceError` since it is used to create a range error instance.
```js
throw new ReferenceError('This is a reference error.');
```
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
- Now, add a event handler to show an alert when the query fails. **Save** the query and **Run** it.
### Creating the Error-Throwing RunJS Query
<div style={{textAlign: 'center'}}>
1. Create a new RunJS query by clicking the `+ Add` button on the query panel.
<img className="screenshot-full" src="/img/how-to/failjs/failjs.gif" alt="Intentionally fail a RunJS query" />
2. Paste the following code into the RunJS query editor. This code utilizes the `ReferenceError` constructor to intentionally generate an error.
```js
throw new ReferenceError('This is a reference error.');
```
</div>
</div>
:::info
Most common use-case for intentionally failing a query is **debugging**.
:::
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Adding an Event Handler for Failure
3. Now, enhance the query by adding an event handler that will display an alert when the query fails.
4. Click the "Run" button to execute the query and observe the intentional error being thrown.
Refer to the screencast below:
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/failjs/failjsn.gif" alt="reate a new RunJS query" />
</div>
</div>
By following these steps, you can effectively simulate errors in your RunJS queries, aiding in the debugging process and improving the overall robustness of your code.

View file

@ -3,197 +3,302 @@ id: run-actions-from-runjs
title: Run Actions from RunJS query
---
# Run `Actions` from RunJS query
ToolJet allows you to execute various [actions](/docs/actions/show-alert) within RunJS queries. This guide outlines the syntax and examples for each action.
You can trigger all the `actions` available in ToolJet from within the `RunJS` query. This guide includes the syntax for each action along with an example.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Run Query
## Run Query Action
To trigger a query, you can use the below functions:
**Syntax:**
```js
queries.queryName.run()
queries.queryName.run();
```
or
```js
await actions.runQuery('<queryName>')
await actions.runQuery('queryName');
```
In the screenshot below, we are triggering two different queries using two different syntax available for `Run Query` action.
**Example:**
In the following screenshot, we demonstrate triggering two different queries, `getCustomers` and `updateCustomers`, using the two available syntax options for the `Run Query` action.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0'}} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/runquery-v2.png" alt="Run Query" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/runqueryn.png" alt="Print data from multiple tabs" />
</div>
### Get Query Data
</div>
In the previous section, we saw how we can trigger queries. Once the queries are triggered, if you want to immediately use the data returned by the query inside the RunJS query, you can use the `getData()`, `getRawData()` and `getLoadingState()` functions:
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
#### Retrieve the latest data of a query:
```js
let response = await queries.getSalesData.run();
// replace getSalesData with your query name
## Set Variable Action
let value = queries.getSalesData.getData();
// replace getSalesData with your query name
```
#### Retrieve the latest raw data of a query:
```js
let response = await queries.getCustomerData.run();
//replace getCustomerData with your query name
let value = queries.getCustomerData.getRawData();
// replace getCustomerData your with query name
```
#### Retreive the loading state of a query:
```js
let response = await queries.getTodos.run()
//replace getTodos with your query name
let value = queries.getTodos.getLoadingState();
//replace getTodos with your query name
```
### Set Variables
To create a variable, you can use the below function:
**Syntax:**
```javascript
actions.setVariable('<variableName>', `<variableValue>`)
actions.setVariable(variableName, variableValue);
```
### Unset Variable
**Example:**
To delete a created variable, you can use the below function:
In this example, we set two variables, `test` and `test2`. Note that `test` contains a numerical value, so it is not wrapped in quotes, while `test2` is a string and is wrapped in quotes.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/setvariablen.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Unset Variable Action
**Syntax:**
```javascript
actions.unSetVariable('<variableName>')
actions.unSetVariable(variableName);
```
### Get Variables
**Example:**
To access variables through immediately after setting them in a RunJS query, you can use the below `getVariable` and `getPageVariable` functions:
In the following screenshot, we unset the variable `test2` that was created in the previous step.
#### Retrieve the current value of a variable:
```js
actions.setVariable('mode','dark');
//replace mode with your desired variable name
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/unsetvarn.png" alt="Print data from multiple tabs" />
</div>
return actions.getVariable('mode');
```
</div>
#### Retrieve the current value of a page-specific variable:
```js
actions.setPageVariable('number',1);
//replace number with your desired variable name
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
return actions.getPageVariable('number');
```
## Logout Action
### Logout
To log out the current logged-in user from the ToolJet, use the below function:
**Syntax:**
```javascript
actions.logout()
actions.logout();
```
**Example:**
### Show Modal
Executing `actions.logout()` will log out the current user from ToolJet and redirect to the sign-in page.
To open a modal using RunJS query, use the below function:
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/logoutn.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Show Modal Action
**Syntax:**
```javascript
actions.showModal('<modalName>')
actions.showModal('modalName');
```
### Close Modal
**Example:**
To close a modal using RunJS query, use the below function:
In this example, a modal named `formModal` is present on the canvas, and we use a RunJS query to show the modal.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/showmodaln.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Close Modal Action
**Syntax:**
```javascript
actions.closeModal('<modalName>')
actions.closeModal('modalName');
```
### Set Local Storage
Set a value in local storage using the below code:
**Example:**
Here, we use a RunJS query to close the modal that was shown in the previous step.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/closemodaln.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Set Local Storage Action
**Syntax:**
```javascript
actions.setLocalStorage('key','value')
actions.setLocalStorage('key', 'value');
```
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/setlocaln.png" alt="Print data from multiple tabs" />
</div>
### Copy to Clipboard
</div>
Use the below code to copy content to the clipboard:
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Copy to Clipboard Action
**Syntax:**
```javascript
actions.copyToClipboard('<contentToCopy>')
actions.copyToClipboard('contentToCopy');
```
### Generate File
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/copytoclip.png" alt="Print data from multiple tabs" />
</div>
The below action can be used to generate a file.
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Generate File Action
**Syntax:**
```js
actions.generateFile('<fileName>', '<fileType>', '<data>')
```
`fileName` is the name that you want to give the file(string), `fileType` can be **csv**, **plaintext**, or **pdf** and `data` is the data that you want to store in the file.
Example for generating CSV file:
```js
actions.generateFile('csvfile1', 'csv', '{{components.table1.currentPageData}}') // generate a csv file named csvfile1 with the data from the current page of table
```
Example for generating Text file:
```js
actions.generateFile('textfile1', 'plaintext', '{{JSON.stringify(components.table1.currentPageData)}}') // generate a text file named textfile1 with the data from the current page of table (stringified)
```
Example for generating PDF file:
```js
actions.generateFile('Pdffile1', 'pdf', '{{components.table1.currentPageData}}') // generate a text file named Pdffile1 with the data from the current page of table
actions.generateFile('fileName', 'fileType', 'data');
```
### Go to App
Example for generating a CSV file:
You can switch to a different application using the below action:
```js
actions.generateFile('csvfile1', 'csv', '{{components.table1.currentPageData}}')
```
Example for generating a Text file:
```js
actions.generateFile('textfile1', 'plaintext', '{{JSON.stringify(components.table1.currentPageData)}}');
```
Example for generating a PDF file:
```js
actions.generateFile('Pdffile1', 'pdf', '{{components.table1.currentPageData}}');
```
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/generatefilen.png" alt="Print data from multiple tabs" />
</div>
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Go to App Action
**Syntax:**
```javascript
actions.goToApp('slug',queryparams)
actions.goToApp('slug', queryparams)
```
- `slug` can be found in URL of the released app after `application/` or in the share modal that opens up when you click on the `Share` button on the top-right of the app-builder
- `queryparams` can be provided in this format - `[{"key":"value"}, {"key2":"value2"}]`
- `slug` can be found in the URL of the released app after the `application/`, or in the `Share` modal. You can also set a custom slug for the app in the `Share` modal or from the global settings in the app builder.
- `queryparams` can be provided like this `[{"key":"value"}, {"key2":"value2"}]`.
- Only the apps that are released can be accessed using this action.
### Show Alert
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/gotoappn.png" alt="Print data from multiple tabs" />
</div>
To show an alert using RunJS query, use the below code:
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Show Alert Action
**Syntax:**
```js
actions.showAlert('<alert type>' , '<message>' )
actions.showAlert(alertType, message); // alert types are info, success, warning, and error
```
Available alert types are `info`, `success`, `warning`, and `danger`.
**Example:**
Example:
```js
actions.showAlert('error' , 'This is an error' )
actions.showAlert('error', 'This is an error')
```
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/run-actions-from-runjs/showalertn.png" alt="Print data from multiple tabs" />
</div>
## Run multiple actions from runjs query
</div>
To run multiple actions from a RunJS query, you'll have to use **async-await** in the function.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
Here is a example code snippet for running the queries and showing alert after specific intervals. Check the complete guide on running queries at specified intervals **[here](/docs/how-to/run-query-at-specified-intervals)**.
## Run Multiple Actions from RunJS Query
To run multiple actions from a RunJS query, use **async-await** in the function. Here's an example code snippet for running queries and showing an alert at specific intervals:
```js
actions.setVariable('interval',setInterval(countdown, 5000));
async function countdown(){
await queries.restapi1.run()
await queries.restapi2.run()
await actions.showAlert('info','This is an information')
actions.setVariable('interval', setInterval(countdown, 5000));
async function countdown() {
await queries.restapi1.run();
await queries.restapi2.run();
await actions.showAlert('info', 'This is an information');
}
```
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Actions on pages
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Switch page
To switch to a page from the JavaScript query, use the following syntax:
```js
await actions.switchPage('<page-handle>')
```
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Switch page with query parameters
Query parameters can be passed through action such as Switch Page. The parameters are appended to the end of the application URL and are preceded by a question mark (?). Multiple parameters are separated by an ampersand (&).
To switch to a page with query parameters from the JavaScript query, use the following syntax:
```js
actions.switchPage('<pageHandle>', [['param1', 'value1'], ['param2', 'value2']])
```
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Set page variable
Page variables are restricted to the page where they are created and cannot be accessed throughout the entire application like regular variables.
To set a page variable from the JavaScript query, use the following syntax:
```js
await actions.setPageVariable('<variablekey>',<variablevalue>)
```
</div>
</div>
This enhanced guide provides a detailed walkthrough of executing various ToolJet actions from RunJS queries.

View file

@ -3,49 +3,82 @@ id: run-query-at-specified-intervals
title: Run query at specified intervals
---
In this how-to guide, we will learn how to make a query trigger at the specific intervals.
In this guide, we'll walk through the process of building a ToolJet application that automates data retrieval at specific intervals. By utilizing the RunJS queries, we can set up intervals for triggering queries, ensuring that the data is fetched dynamically and efficiently.
- Let's go to the ToolJet dashboard and **create a new application**
- Once the app builder opens up, drag a **table** component to canvas
- Now, create a new REST API query from the query panel at the bottom of the app builder. We will be using the data from the mock **REST API** and then load the data on the table. Let's create a REST API, choose `GET` method from the dropdown, enter the endpoint `(https://jsonplaceholder.typicode.com/posts)`, name the query `post` and then **save and run** it
<div style={{textAlign: 'center'}}>
## Step 1: Create a new application
<img className="screenshot-full" src="/img/how-to/setinterval/query.png" alt="REST API query" width="600" />
Begin by creating a new application in the ToolJet dashboard. Once the app builder opens, Drag a table component onto the canvas. This component will display the data fetched from the REST API query.
</div>
- Go to the **Table properties** and add connect the query data to table by adding value to **table data** property which is `{{queries.post.data}}`
<div style={{textAlign: 'center'}}>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setinterval/table.png" alt="Table Component With Data" />
</div>
<img className="screenshot-full" src="/img/how-to/setinterval/data.png" alt="REST API query" width="300" />
## Step 2: Set Up a REST API Query
</div>
From the query panel, create a new REST API query. Utilize mock REST API data by choosing the 'GET' method and specifying the endpoint (e.g., `https://jsonplaceholder.typicode.com/posts`). Name the query 'post' and `Run` the query to ensure that the data is fetched successfully.
- Now, we will create a RunJS query that will first set a variable called `interval` which will include the value returned by the `setInterval()` method that calls a function `countdown` at specified intervals. The countdown function has the code to trigger the `post` query that we created in the previous step.
```js
actions.setVariable('interval',setInterval(countdown, 5000));
function countdown(){
queries.post.run()
}
```
- Or use **async**-**await** in the function, if you're triggering multiple actions:
```js
actions.setVariable('interval',setInterval(countdown, 5000));
async function countdown(){
await queries.restapi1.run()
await queries.restapi2.run()
await actions.showAlert('info','This is an information')
}
```
- Go to the **Advanced** tab of the query, enable `Run query on page load?` this will trigger this RunJS query when the app is loaded. Name the query as `set` and **Save** it. Note that you will have to save the query and not `Save and Run` because doing it will trigger the query and you won't be able to stop the query unless you reload the page or go back to dashboard.
<div style={{textAlign: 'center'}}>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setinterval/queryprev.png" alt="Table Component With Data" />
</div>
<img className="screenshot-full" src="/img/how-to/setinterval/set.png" alt="REST API query" width="700" />
## Step 3: Configure Table Properties
</div>
- To prevent the query from triggering indefinitely, we will create another RunJS query that will make use of `clearInterval()` method. In this method we will get the value from the variable that we created in `set` query. Save this query as `clear`.
```js
clearInterval(variables.interval)
```
- Finally, let's add a **button** on to the canvas and add the **event handler** to the button to run the `clear` query.
- Now, whenever the app will be loaded the **set** query will be triggered and will keep triggering the `post` query at the specified intervals. Whenever the user wants to **stop** the query they can click on the **button** to trigger the **clear** query which will clear the interval.
In the Table properties, link the query data to the table by setting the 'table data' property to `{{queries.post.data}}`. This establishes the connection between the REST API query and the table component.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setinterval/queryp.png" alt="Table Component With Data" />
</div>
## Step 4: Implement the RunJS Query
Create a RunJS query to set up intervals for triggering the REST API query. Use the following script:
```js
actions.setVariable('interval', setInterval(countdown, 5000)); // 5000ms = 5 seconds
function countdown(){ // Function to trigger the REST API query
queries.post.run(); // action to run the REST API query
}
```
Adjust the interval duration according to your needs. Optionally, utilize `async` and `await` for multiple actions within the countdown function.
```js
actions.setVariable('interval',setInterval(countdown, 5000));
async function countdown(){
await queries.restapi1.run()
await queries.restapi2.run()
await actions.showAlert('info','This is an information')
}
```
## Step 5: Advanced Configuration
From the Settings section of the RunJS query, enable 'Run query on page load.' This ensures that the query is triggered when the application is loaded. Rename the query as 'setInterval' to complete the configuration.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setinterval/settings.png" alt="Table Component With Data" />
</div>
## Step 6: Prevent Indefinite Triggering
Create another RunJS query named 'clearInrternal' to stop the query from triggering indefinitely. Use the `clearInterval()` method to clear the interval. This method retrieves the value from the variable set in the 'setInterval' query.
```js
clearInterval(variables.interval);
```
## Step 7: Add a Button
Drag a button on the canvas to act as a user-triggered stop mechanism. Attach an event handler to execute the 'clear' query when the button is clicked.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/setinterval/clearint.png" alt="Table Component With Data" />
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
By following these steps, your ToolJet application will dynamically fetch data at specified intervals, providing an efficient and automated user experience.
</div>

View file

@ -3,66 +3,87 @@ id: use-server-side-pagination
title: Using server side pagination for efficient data handling in tables
---
In this guide we will learn how to use server side pagination in table component. This will be helpful if you have a large data set and you want to load data in chunks. This will also help you to improve the performance of your application. This guide will be helpful if you are using datasources like MySQL, PostgreSQL, MSSQL, MongoDB, etc. in which you can use `limit` and `offset` to fetch data in chunks. We have also included an example to load data from Google Sheets in chunks.
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
## Loading data from PostgreSQL in chunks
In this guide we will learn how to use server side pagination in table component. This will be helpful if you have a large data set and you want to load data in chunks. This will also help you to improve the performance of your application. This guide will be helpful if you are using data sources like MySQL, PostgreSQL, MSSQL, MongoDB, etc. in which you can use `limit` and `offset` to fetch data in chunks. We have also included an example to load data from Google Sheets in chunks.
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
### Loading data from PostgreSQL in chunks
- Let's say you have a table `users` in your PostgreSQL database and you want to load data from this table in chunks. You can use `limit` and `offset` to fetch data in chunks. Here is the SQL query to fetch data in chunks:
```sql
SELECT *
FROM users
ORDER BY id
LIMIT 100 OFFSET {{(components.table1.pageIndex-1)*100}};
```
The query will fetch 100 rows at a time from the postgresql users table, and the number of rows returned is determined by the current value of `pageIndex`(exposed variable) in the Table component.
1. `ORDER BY id`: This part of the query specifies the ordering of the result set. It orders the rows based on the `id` column. You can replace `id` with the appropriate column name based on how you want the rows to be ordered.
2. `LIMIT 100`: The `LIMIT` clause limits the number of rows returned to 100. This means that each time the query is executed, it will fetch 100 rows from the table.
3. `OFFSET {{(components.table1.pageIndex-1)*100}}`: The `OFFSET` clause determines where to start fetching rows from the result set. In this case, the offset value is calculated based on the `pageIndex`(exposed variable) in the Table component. The formula `(components.table1.pageIndex-1)*100` calculates the starting row number for the current page. Since the index is 1-based, we subtract 1 from `pageIndex` to convert it to a 0-based index. Then we multiply it by 100 to get the offset for the current page. For example, if `pageIndex` is 1, the offset will be 0, which means it will fetch rows from the first 100 rows. If `pageIndex` is 2, the offset will be 100, which means it will fetch rows from rows 101 to 200, and so on.
```sql title="PostgreSQL query"
SELECT *
FROM users
ORDER BY id
LIMIT 100 OFFSET {{(components.table1.pageIndex-1)*100}};
```
The query will fetch 100 rows at a time from the postgresql users table, and the number of rows returned is determined by the current value of `pageIndex`(exposed variable) in the Table component.
1. `ORDER BY id`: This part of the query specifies the ordering of the result set. It orders the rows based on the `id` column. You can replace `id` with the appropriate column name based on how you want the rows to be ordered.
2. `LIMIT 100`: The `LIMIT` clause limits the number of rows returned to 100. This means that each time the query is executed, it will fetch 100 rows from the table.
3. `OFFSET {{(components.table1.pageIndex-1)*100}}`: The `OFFSET` clause determines where to start fetching rows from the result set. In this case, the offset value is calculated based on the `pageIndex`(exposed variable) in the Table component. The formula `(components.table1.pageIndex-1)*100` calculates the starting row number for the current page. Since the index is 1-based, we subtract 1 from `pageIndex` to convert it to a 0-based index. Then we multiply it by 100 to get the offset for the current page. For example, if `pageIndex` is 1, the offset will be 0, which means it will fetch rows from the first 100 rows. If `pageIndex` is 2, the offset will be 100, which means it will fetch rows from rows 101 to 200, and so on.
</div>
<div style={{paddingTop:'24px', paddingBottom:'24px'}}>
- Create a new query that will return the count of the records on the `users` table in postgresql db. This query will be used to calculate the total number of pages in the Table component. Here is the SQL query to fetch the count of records:
```sql
SELECT COUNT(*)
FROM users;
```
```sql
SELECT COUNT(*)
FROM users;
```
- Enable the option to run the query on page load so that the query is executed when the app loads.
- Add an event handler to run the query that fetches data from the PostgreSQL table and then save the changes.
- Once the count query is created, execute it to get the total number of records. You can dynamically access the count of records using `{{queries.<countquery>.data[0].count}}`.
</div>
### Edit the Table component
**Now, let's edit the properties of the Table component:**
- Set the value of the **Table data** property to `{{queries.<postgresquery>.data}}`
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/server-side/data.png" alt="Table data"/>
</div>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/server-side/data.png" alt="Table data" />
</div>
- Enable the **server-side pagination** option
- Click on the `Fx` next to **Enable previous page button** and set it's value to `{{components.table1.pageIndex >=2 ? true : false}}`. This condition disables the previous page button when the current page is page `1`.
- Click on the `Fx` next to **Enable next page button** and set it's value to `{{components.table1.pageIndex < queries.<countquery>.data[0].count/100 ? true : false}}`. This condition disables the next page button when the current page is the last page.
- Set the value of the **Total records server side** property to `{{queries.<countquery>.data[0].count}}`. This will set the total number of records in the Table component.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/server-side/pagination.png" alt="Table data"/>
</div>
- Enable the **Server-side pagination** option
- Click on the `Fx` next to **Enable previous page button** and set the value as below. This condition disables the previous page button when the current page is page `1`.
```js
{{components.table1.pageIndex >=2 ? true : false}}
```
- Click on the `Fx` next to **Enable next page button** and set it's value as below. This condition disables the next page button when the current page is the last page.
```js
{{components.table1.pageIndex < queries.<countquery>.data[0].count/100 ? true : false}}
```
- Set the value of the **Total records server side** property as below. This will set the total number of records in the Table component.
```js
{{queries.<countquery>.data[0].count}}
```
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/server-side/pagination.png" alt="Table data" />
</div>
- Now, the last step is to set the **loading state** and add the **event handler**:
- Loading State: Set the loading state property to `{{queries.<postgresquery>.isLoading}}`. This will show the loading indicator on the table component when the query is executing.
- Event Handler: Select the **Page changed** event and choose the **Run Query** action. Then, select the **Query** from the dropdown that fetches data from the PostgreSQL table
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/server-side/event.png" alt="Table data" />
</div>
- **Loading State**: This will show the loading indicator on the table component when the query is executing. Set the loading state property as:
```js
{{queries.<postgresquery>.isLoading}}
```
- **Event Handler**: Select the **Page changed** event and choose the **Run Query** action. Then, select the **Query** from the dropdown that fetches data from the PostgreSQL table
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/server-side/event.png" alt="Table data" />
</div>
Now, whenever the page is changed, the query will be executed, and the data will be fetched from the PostgreSQL table in chunks.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/server-side/change.gif" alt="Table data" />
</div>
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/server-side/change.gif" alt="Table data" />
</div>

View file

@ -1,60 +1,56 @@
---
id: use-to-py-function-in-runpy
title: "Use the to_py() Function in RunPy: Converting JavaScript Objects to Python"
title: "Utilize the to_py() Function in RunPy: Translating JavaScript Objects to Python"
---
This how-to guide will demonstrate the usage of `to_py()` function in RunPy queries for converting the JavaScript objects to Python.
This guide demonstrates the utilization of the `to_py()` function in RunPy queries for converting JavaScript objects into their corresponding Python representations.
## to_py() function
## The to_py() Function
The **to_py()** function in **Pyodide** is the counterpart of the **to_js()** function. It is used to convert JavaScript objects into their equivalent Python representations. This conversion is necessary when it is required to work with JavaScript objects within the Pyodide environment and manipulate them using Python code.
The **to_py()** function within the **Pyodide** library serves as the counterpart to the **to_js()** function. Its purpose is to transform JavaScript objects into their equivalent Python structures. This conversion becomes essential when handling JavaScript objects within the Pyodide environment and manipulating them using Python code.
Similar to **to_js()**, **to_py()** performs the necessary mapping and conversion of data types between JavaScript and Python. It converts JavaScript objects, arrays, and other JavaScript data structures into their Python equivalents.
Similar to **to_js()**, **to_py()** facilitates the mapping and conversion of data types between JavaScript and Python. It effectively converts JavaScript objects, arrays, and other data structures into their Python counterparts.
:::tip
Check **[RunPy](/docs/data-sources/run-py)** doc to learn more.
:::
**Note**: Refer to the **[RunPy](/docs/data-sources/run-py)** documentation for a more in-depth understanding.
## Using to_py() function
## Using the to_py() Function
Here's an example demonstrating the usage of to_py():
Here's an example demonstrating the application of the to_py() function:
```python
import pyodide
import pyodide # Import the Pyodide library
def to_py(js_object):
return dict(js_object)
def to_py(js_object): # Define a function to convert JavaScript objects to Python dictionaries
return dict(js_object) # Convert the JavaScript object to a Python dictionary
my_js_object = {"name": "John", "age": 25, "country": "USA"}
my_js_object = {"name": "John", "age": 25, "country": "USA"} # Create a JavaScript object
my_py_dict = to_py(my_js_object)
my_py_dict = to_py(my_js_object) # Convert the JavaScript object to a Python dictionary
my_py_dict
my_py_dict # Return the Python dictionary
```
In this example, a JavaScript object my_js_object is created using the Object.fromEntries() method from JavaScript. It represents a dictionary-like structure. The to_py() function is then used to convert the JavaScript object into a Python dictionary my_py_dict.
In this example, a JavaScript object `my_js_object` is created using the Object.fromEntries() method, representing a dictionary-like structure. The to_py() function is then employed to convert this JavaScript object into a Python dictionary, resulting in `my_py_dict`.
The output will be:
```json
{'name': 'John', 'age': 25, 'country': 'USA'}
```
By using to_py(), JavaScript objects can seamlessly convert into Python representations and work with them using Python code within the Pyodide environment.
By leveraging to_py(), JavaScript objects can seamlessly transition into Python representations, allowing for manipulation using Python code within the Pyodide environment.
Both **to_js()** and **to_py()** functions provide a convenient way to exchange data between Python and JavaScript when working with Pyodide, enabling to leverage the strengths of both languages in a unified environment.
Both **to_js()** and **to_py()** functions offer a convenient means to exchange data between Python and JavaScript in Pyodide, enabling the utilization of both languages' strengths in a unified environment.
## Why use of to_py() is required?
## Why the Use of to_py() is Essential?
When previewing the results of a RunPy query, the discrepancy between the JSON and Raw tabs can arise due to the way data is converted and displayed in Pyodide. By default, **Python dictionaries** are converted to **Javascript Map objects** in Pyodide. This conversion is performed *to ensure compatibility between the two languages*.
When previewing results in a RunPy query, discrepancies between the JSON and Raw tabs may arise due to the conversion and display mechanisms in Pyodide. By default, **Python dictionaries** are converted to **JavaScript Map objects** in Pyodide, ensuring compatibility between the two languages.
As a result, when viewing the data in the **JSON** tab, it is presented in the format of JavaScript objects, represented by **()** symbols. On the other hand, the **Raw** tab displays the raw representation of the returned data **[{}, {}, ...],** which may show Python dictionaries in their original form with **{}** symbols.
Consequently, the **JSON** tab presents data in the format of JavaScript objects, denoted by **()** symbols, while the **Raw** tab displays the raw representation as **[{}, {}, ...],** showing Python dictionaries in their original form with **{}** symbols.
In this case, both representations are correct. The JSON tab presents the converted data in a format that is compatible with JavaScript, while the Raw tab displays the original Python dictionaries. The choice depends on the user's specific use case and whether they need to work with the data in a **Javascript context** or **Python context**.
Both representations are correct, with the JSON tab showcasing converted data compatible with JavaScript, and the Raw tab displaying the original Python dictionaries. The choice depends on the user's use case and whether they need to work with the data in a **JavaScript context** or **Python context**.
To ensure consistency between the JSON and Raw representations, **to_js()** function provided by Pyodide can be used to explicitly convert Python dictionaries to JavaScript objects. This will help align the representations and ensure that the data is in the desired format.
To maintain consistency between JSON and Raw representations, the **to_js()** function provided by Pyodide can explicitly convert Python dictionaries to JavaScript objects. This ensures alignment between representations and guarantees that the data is in the desired format.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/how-to/to_py/topy.gif" alt="Use the to_py() Function in runPy: Converting JavaScript Objects to Python" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/how-to/to_py/topy.gif" alt="Print data from multiple tabs" />
</div>

View file

@ -5,6 +5,10 @@ title: Workspace Constants
Workspace constants are predefined values(usually tokens/secret keys/API keys) that can be used across your application to maintain consistency and facilitate easy updates. They allow you to store important data or configurations that should remain unchanged during the application's runtime. This doc will guide you through the usage and management of workspace constants within your workspaces.
:::danger
Workspace constants are handled server-side and are not intended for use in query transformations or RunJS and RunPy queries. For these operations, employ variables and page variables instead.
:::
## Environment-Specific Configurations
Users can define environment-specific configurations by setting different values for constants across environments. It is useful for managing sensitive information such as API keys, database credentials, or external service endpoints. For Community edition only production environment is available and for Cloud/EE we will have multi environments (development, staging, production).

View file

@ -21,7 +21,7 @@ All the datasource credentials are securely encrypted using `aes-256-gcm`. The c
- **TLS**: If you are using ToolJet cloud, all connections are encrypted using TLS. We also have documentation for setting up TLS for self-hosted installations of ToolJet.
- **Audit logs**: Audit logs are available on the enterprise edition of ToolJet. Every user action is logged along with the IP addresses and user information.
- **Request logging**: All the requests to server are logged. If self-hosted, you can easily extend ToolJet to use your preferred logging service. ToolJet comes with built-in Sentry integration.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (3.129.198.40) so that your datasources are not exposed to the public.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (34.86.81.252) so that your datasources are not exposed to the public.
- **Backups**: ToolJet cloud is hosted on AWS using EKS with autoscaling and regular backups.
If you notice a security vulnerability, please let the team know by sending an email to `security@tooljet.com`.

View file

@ -46,13 +46,25 @@ Follow the steps below to deploy ToolJet on Cloud run with `gcloud` CLI.
<img className="screenshot-full" src="/img/cloud-run/ingress-auth.png" alt="ingress-auth" />
</div>
4. Under containers tab, please make sure the port is set 3000 and command `npm, run, start:prod` is entered in container argument field with CPU capacity is set to 2GiB.
4. Under containers tab, please make sure the port is set to 3000 and command `npm, run, start:prod` is entered in container argument field with CPU capacity set to 2GiB:
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/cloud-run/port-and-capacity-postgrest.png" alt="port-and-capacity-tooljet" />
<img className="screenshot-full" src="/img/cloud-run/port-and-capacity-postgrest-v2.png" alt="port-and-capacity-tooljet" />
</div>
- If the command mentioned above is not compatible, please use the following command structure instead:
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/cloud-run/port-and-capacity-postgrest-alternative-command.png" alt="port-and-capacity-tooljet-alternative-command" />
</div>
- Should you encounter any migration issues, please execute the following command. Be aware that executing this command may cause the revision to break. However, modifying the command back to `npm, run, start:prod` will successfully reboot the instance:
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/cloud-run/port-and-capacity-postgrest-migration-fix-command.png" alt="port-and-capacity-tooljet-migration-fix-command" />
</div>
5. Under environmental variable please add the below Tooljet application variables. You can also refer env variable [**here**](/docs/setup/env-vars).
Update `TOOLJET_HOST` environment variable if you want to use the default url assigned with Cloud run after the initial deploy.

View file

@ -7,71 +7,101 @@ title: Managing Users and Groups
## Managing Users
Admin of a workspace can add users to the workspace. To manage the users in your workspace, just go to the **Workspace Settings** from the left sidebar on the dashboard and click on the **Users** option.
Admins of a workspace can invite users to the workspace or archive/unarchive the existing users of a workspace. To manage users in a workspace, go to the **Workspace Settings** from the left sidebar on the dashboard and select **Users**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/usersnew.png" alt="Manage Users" />
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/users3.png" alt="Manage Users" />
</div>
### Inviting users
### Inviting Users
Admins can invite anyone to a workspace using the email address. To invite a user:
- On the **Users** page click on the `Add users` button.
- Click on the `Add users` button on the top right corner of the **Users** page.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/addusersbutton.png" alt="Manage Users" />
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/addusers.png" alt="Manage Users" />
</div>
- A drawer from the right will open, navigate to the **Invite with email** tab. Fill in the required information for the new user, including their Full Name, Email address, and select the desired group(s) from the dropdown menu to assign them. Once you have entered all the details, proceed by clicking the **Invite Users** button.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/groupsdrop.png" alt="add new user" />
</div>
- On clicking the `Add users` button, a drawer will open from the right. Click on the **Invite with email** tab. Fill in the required information for the new user, including their Full Name, Email address, and select the desired group(s) from the dropdown menu to assign them. Once you have entered all the details, proceed by clicking the **Invite Users** button.
- An email including the **Invite Link** to join your workspace will be send to the created user. The status will turn from **invited** to **active** after the user successfully joins your workspace using the invite link.
:::tip
You can also copy the invitation url by clicking on the copy icon next to `invited` status of the created user.
:::
Note: The **All Users** group is the default group for all the users in a workspace. You can also create a new group and assign it to the user.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/invited2.png" alt="add new user" />
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/invitemodal.png" alt="add new user" />
</div>
- You can also **Bulk Invite Users** by editing and uploading the sample CSV file including all the users details. Click on the `Add users` button and on the drawer, click on the **Upload CSV file** tab.
- An email including the **Invite Link** to join the workspace will be send to the invited user. The status will turn from **Invited** to **Active** after the user successfully joins your workspace using the invite link.
**TIP**: You can also copy the invitation url by clicking on the `Copy link` next to `Invited` status of the invited user.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/bulknew.png" alt="add new user" />
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/copylink.png" alt="add new user" />
</div>
### Disabling a user's access
You can disable any active user's access to your workspace by clicking on the **Archive** button and the status of the user will change from **active** to **archived**.
<div style={{textAlign: 'center'}}>
- You can also **Bulk Invite Users** by editing and uploading the sample CSV file including all the users details. Click on the `Add users` button and select the **Bulk Invite** tab.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/archivenew.png" alt="archived"/>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/bulkinviten.png" alt="add new user" />
</div>
</div>
### Enabling a user's access
### Edit User Details
Similar to archiving a user's access, you can enable it again by clicking on **Unarchive**. The status of user will change from **archived** to **invited** and the user will have to join again using the invite link received via the e-mail.
Admins of a workspace can edit the details of any user in their workspace. The details include **adding** or **removing** the user from a group. To edit the details of a user:
<div style={{textAlign: 'center'}}>
- Go to the **Users** settings from the **Workspace Settings**.
- Click on the kebab menu next to the user you want to edit and select **Edit user details**.
- A drawer will open from the right. Admins can add or remove the user from a group. Once you have made the changes, click on the **Update** button.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/unarchivenew.png" alt="status" />
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/edituserdetails.png" alt="edit user" />
</div>
</div>
### Archive User from a workspace
Admins of a workspace can archive any user from their workspace. Archiving a user will disable their access to the workspace.
**Info**: An archived user from a workspace can still be invited to the other workspaces unless they are archived at instance level from the **[Settings](/docs/Enterprise/superadmin#settings)** page.
To archive a user:
- Go to the **Users** page from the **Workspace Settings**.
- Click on the kebab menu next to the user you want to archive and select **Archive**.
- Once the user is archived, the status will change from **Active** to **Archived**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/archiveuser.png" alt="archive user" />
</div>
### Unarchive User from a workspace
Admins of a workspace can unarchive any user from their workspace. Unarchiving a user will enable their access to the workspace.
**Info**: A user who is **Archived** at instance level from the **[Settings](/docs/Enterprise/superadmin#settings)** page, if **Unarchived** from a workspace, will automatically be **Unarchived** at instance level as well.
To unarchive a user:
- Go to the **Users** page from the **Workspace Settings**.
- Click on the kebab menu next to the user that is archived and select **Unarchive** option.
- Once the user is unarchived, the status will change from **Archived** to **Invited**. The user will have to join again using the invite link received via the e-mail.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tutorial/manage-users-groups/unarchiveuser.png" alt="unarchive user" />
</div>
## Managing Groups

View file

@ -14,6 +14,7 @@ Transformations can be enabled on queries to transform the query results. ToolJe
:::caution
- Every transformation is scoped to the query it's written for.
- Workspace Constants are resolved server side and will not work with transformations.
- Actions and CSA(Component Specific Actions) cannot be called within the transformation, they can only be called within **[RunJS](/docs/data-sources/run-js)** query or **[RunPy](/docs/data-sources/run-py)** query.
:::

View file

@ -3,30 +3,69 @@ id: password-login
title: Password Login
---
# Password Login
## Enable Password Login
Password login is enabled by default for all workspaces. User with admin privilege can enable/disable it.
Password login is a method of user authentication where user can login using their email and password. This method is enabled by default for all workspaces. User with admin privilege can enable/disable it.
- Go to the **Workspace Settings** (⚙️) from the left sidebar in the ToolJet dashboard
<div style={{textAlign: 'center'}}>
- Go to **Workspace Settings** > **SSO** > **General Settings**.
<img className="screenshot-full" src="/img/sso/general/workside2.png" alt="General Settings: SSO" width="500"/>
- Under **General Settings** section, toggle **Password Login** to enable/disable it.
</div>
<div style={{textAlign: 'center'}}>
- Select `SSO` from sidebar
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/general/password-login.png" alt="General Settings: Password login" />
<img className="screenshot-full" src="/img/sso/general/sso2.png" alt="General Settings: SSO" width="500"/>
</div>
- Select **Password Login**. You can enable/disable it
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/general/password.png" alt="General Settings: SSO" />
</div>
</div>
## Retry limits
The user password authentication method will be disabled after predefined numbers of wrong password attempts. This feature can be disabled using setting `DISABLE_PASSWORD_RETRY_LIMIT` to `true` in environment variables. Number of retries allowed will be 5 by default, it can be override by `PASSWORD_RETRY_LIMIT` environment variable.
## Reset Password
There are two ways through which a user can reset their password. The first method is where user can reset their password by themselves. The second method is where a **Super Admin** can reset password for a user.
### 1. Forgot Password
- On the login page, click on the **Forgot Password**.
- Enter the registered email address associated with the account and then click on the **Send a reset link** button.
- Receive a password reset link via email.
- Click on the link to be directed to the password reset page.
- Follow the prompts to set a new password.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/general/forgot-password.png" alt="General Settings: Reset Password" />
</div>
### 2. **Super Admin**
- Reach out to the **[Super Admin](/docs/Enterprise/superadmin)** of the workspace.
- The **Super Admin** can reset the password for the user from the **Settings** > **All Users** section.
- Select the user for whom the password needs to be reset.
- Click on the kebab icon(three dots) on the right side of the user's name and select **Reset Password**.
- A modal will appear with two options to reset the password: **Automatically generate a password** and **Create password**.
#### Automatically Generate Password
- Selecting this option will automatically generate a new password for the user.
- Click on the **Reset** button to reset the password and the new password will be displayed in the modal.
- Super Admin can copy this password and provide it to the user securely.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/general/auto-password.png" alt="General Settings: Reset Password" />
</div>
#### Create Password
- Selecting this option will allow the Super Admin to create a new password for the user.
- Enter the new password and click on the **Reset** button.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/general/create-password.png" alt="General Settings: Reset Password" />
</div>

View file

@ -3,65 +3,115 @@ id: github
title: GitHub
---
# GitHub Single Sign-on
# GitHub Single Sign-on Configuration
To enable GitHub Single Sign-on (SSO) for your ToolJet instance, follow these steps:
1. From the ToolJet dashboard, go to **Settings** (⚙️) from the bottom of the left sidebar and select the **Workspace Settings**.
- Go to the **Workspace Settings** (⚙️) from the left sidebar in the ToolJet dashboard
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/general/workside2.png" alt="General Settings: SSO" width="500"/>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/sso/git/workset.png" alt="GitHub SSO" />
</div>
- Select `SSO` from sidebar and then select **GitHub**. GitHub login will be **disabled** by default,
2. In the **Workspace Settings**, select **SSO** from the sidebar and then select **GitHub**. GitHub login will be **Disabled** by default, **Enable** it and you will see the generated `Redirect URL`.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/git/gitssov22.png" alt="General Settings: SSO" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/sso/git/ssogit.png" alt="GitHub SSO" />
</div>
- Enable GitHub. You can see `Redirect URL` generated
3. Now go to the **[GitHub Developer settings](https://github.com/settings/developers)** and navigate to `OAuth Apps` and create a new project.
- The **Client ID** will be generated automatically.
- Generate the **Client Secret** by clicking the `Generate new client secret` button. Copy the **Client Secret** and save it for later use.
- Enter the **App Name**, **Homepage URL**, and **Authorization callback URL**. The **Authorization callback URL** should be the generated `Redirect URL` in the GitHub manage SSO page.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/git/gitsso2v22.png" alt="General Settings: SSO" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/sso/git/gitdev.png" alt="GitHub SSO" />
</div>
- Go to **[GitHub Developer settings](https://github.com/settings/developers)** and navigate to `OAuth Apps` and create a project. `Authorization callback URL` should be the generated `Redirect URL` in Git manage SSO page.
4. Open the ToolJet's GitHub SSO settings and enter the obtained **Client ID** and **Client Secret**.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/sso/git/create-project.png" alt="General Settings: SSO" width="500" />
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/sso/git/ssogit.png" alt="GitHub SSO" />
</div>
- Open the application details, and you can see the `Client ID`
<div style={{textAlign: 'center'}}>
5. If you are using **GitHub Enterprise** self-hosted, enter the `Host Name`. The host name should be a URL and should not end with `/`, for example, `https://github.tooljet.com`. If it is not self-hosted, you can skip this field.
<img className="screenshot-full" src="/img/sso/git/client-id.png" alt="General Settings: SSO" width="700"/>
6. Finally, click on the **Save changes** button and the GitHub sign-in button will now be available in your ToolJet login screen.
</div>
7. Obtain the Login URL from the **[General Settings](/docs/user-authentication/general-settings#login-url)** of the SSO page.
- Then create `Client secrets` by clicking `Generate new client secret`
<div style={{textAlign: 'center'}}>
### Setting Default SSO
<img className="screenshot-full" src="/img/sso/git/client-secret.png" alt="General Settings: SSO" width="700"/>
To set GitHub as the default SSO for the instance, use the following environment variables:
</div>
Lastly, enter **Client Id** and **Client Secret** in GitHub manage SSO page and save.
The GitHub sign-in button will now be available in your ToolJet login screen.
:::info
Should configure `Host Name` if you are using GitHub Enterprise self hosted. Host name should be a URL and should not ends with `/`, example: `https://github.tooljet.com`
:::
## Setting default SSO
To set GitHub as default SSO for the instance use environment variable.
| variable | description |
| Variable | Description |
| ------------------------------------- | ----------------------------------------------------------- |
| SSO_GIT_OAUTH2_CLIENT_ID | GitHub OAuth client id |
| SSO_GIT_OAUTH2_CLIENT_SECRET | GitHub OAuth client secret |
| SSO_GIT_OAUTH2_HOST | GitHub OAuth host name if GitHub is self hosted |
| SSO_GIT_OAUTH2_CLIENT_ID | GitHub OAuth client ID |
| SSO_GIT_OAUTH2_CLIENT_SECRET | GitHub OAuth client secret |
| SSO_GIT_OAUTH2_HOST | GitHub OAuth host name if GitHub is self-hosted |
**Redirect URL should be `<host>/sso/git`**
**Redirect URL should be `<host>/sso/git`**
### Exposed ssoUserInfo
Once the GitHub SSO is configured (on ToolJet version **`2.28.0-ee2.12.2`** or above), ToolJet will expose the user info returned by the GitHub. The user info will be available under the `ssoUserInfo` property of the `currentUser` global variable. Check the **[Inspector](/docs/how-to/use-inspector)** doc to learn more.
The exposed user info can be dynamically accessed throughout the apps using JS **`{{globals.currentUser.ssoUserInfo.<key>}}`**
The following is an example of the user info returned by GitHub:
| Key | Description | Syntax to access |
|:--- |:----------- |:---------------- |
| **login** | GitHub username | `{{globals.currentUser.ssoUserInfo.login}}` |
| **id** | GitHub user ID | `{{globals.currentUser.ssoUserInfo.id}}` |
| **node_id** | GitHub user node ID | `{{globals.currentUser.ssoUserInfo.node_id}}` |
| **avatar_url** | GitHub user avatar URL | `{{globals.currentUser.ssoUserInfo.avatar_url}}` |
| **gravatar_id** | GitHub user gravatar ID | `{{globals.currentUser.ssoUserInfo.gravatar_id}}` |
| **url** | GitHub user URL | `{{globals.currentUser.ssoUserInfo.url}}` |
| **html_url** | GitHub user HTML URL | `{{globals.currentUser.ssoUserInfo.html_url}}` |
| **followers_url** | GitHub user followers URL | `{{globals.currentUser.ssoUserInfo.followers_url}}` |
| **following_url** | GitHub user following URL | `{{globals.currentUser.ssoUserInfo.following_url}}` |
| **gists_url** | GitHub user gists URL | `{{globals.currentUser.ssoUserInfo.gists_url}}` |
| **starred_url** | GitHub user starred URL | `{{globals.currentUser.ssoUserInfo.starred_url}}` |
| **subscriptions_url** | GitHub user subscriptions URL | `{{globals.currentUser.ssoUserInfo.subscriptions_url}}` |
| **organizations_url** | GitHub user organizations URL | `{{globals.currentUser.ssoUserInfo.organizations_url}}` |
| **repos_url** | GitHub user repos URL | `{{globals.currentUser.ssoUserInfo.repos_url}}` |
| **events_url** | GitHub user events URL | `{{globals.currentUser.ssoUserInfo.events_url}}` |
| **received_events_url** | GitHub user received events URL | `{{globals.currentUser.ssoUserInfo.received_events_url}}` |
| **type** | GitHub user type | `{{globals.currentUser.ssoUserInfo.type}}` |
| **site_admin** | GitHub user site admin | `{{globals.currentUser.ssoUserInfo.site_admin}}` |
| **name** | GitHub user name | `{{globals.currentUser.ssoUserInfo.name}}` |
| **company** | GitHub user company | `{{globals.currentUser.ssoUserInfo.company}}` |
| **blog** | GitHub user blog | `{{globals.currentUser.ssoUserInfo.blog}}` |
| **location** | GitHub user location | `{{globals.currentUser.ssoUserInfo.location}}` |
| **email** | GitHub user email | `{{globals.currentUser.ssoUserInfo.email}}` |
| **hireable** | GitHub user hireable | `{{globals.currentUser.ssoUserInfo.hireable}}` |
| **bio** | GitHub user bio | `{{globals.currentUser.ssoUserInfo.bio}}` |
| **twitter_username** | GitHub user twitter username | `{{globals.currentUser.ssoUserInfo.twitter_username}}` |
| **public_repos** | GitHub user public repos | `{{globals.currentUser.ssoUserInfo.public_repos}}` |
| **public_gists** | GitHub user public gists | `{{globals.currentUser.ssoUserInfo.public_gists}}` |
| **followers** | GitHub user followers | `{{globals.currentUser.ssoUserInfo.followers}}` |
| **following** | GitHub user following | `{{globals.currentUser.ssoUserInfo.following}}` |
| **created_at** | GitHub user created at | `{{globals.currentUser.ssoUserInfo.created_at}}` |
| **updated_at** | GitHub user updated at | `{{globals.currentUser.ssoUserInfo.updated_at}}` |
| **access_token** | GitHub user access token. Sensitive information of a logged-in user. | `{{globals.currentUser.ssoUserInfo.access_token}}` |
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/sso/git/ssogithub.png" alt="GitHub SSO" />
</div>
### Example: Getting user information using the access_token
Once a user is logged in to ToolJet using GitHub SSO, the access token of the user becomes available. This access token can be utilized within ToolJet apps to retrieve detailed user information from the GitHub API.
1. Log in to ToolJet using GitHub Single Sign-on as outlined in the previous setup steps.
2. Create a new ToolJet application and then create new REST API query. Set the method to `GET` and the URL to `https://api.github.com/user/followers`. This API call will return the list of followers for the logged-in GitHub user.
3. In the Headers section of the query, include the **key** `Authorization` and set the **value** to `Bearer {{globals.currentUser.ssoUserInfo.access_token}}`. This will pass the user's GitHub access token as a Bearer token in the request header.
5. Execute the query to fetch the list of followers for the logged-in user. The response will contain the list of followers for the authenticated GitHub user.
<div style={{textAlign: 'center'}}>
<img style={{ border:'0', marginBottom:'15px', borderRadius:'5px', boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)' }} className="screenshot-full" src="/img/sso/git/queryresults.png" alt="GitHub SSO" />
</div>

View file

@ -1,6 +1,6 @@
---
id: setup
title: Setup
title: OpenID Setup
---
<div className='badge badge--primary heading-badge'>Available on: Paid plans</div>
@ -52,6 +52,7 @@ The following is an example of the user info returned by Google OpenID provider:
| **hd** | End-User's hosted domain, if any. | `{{globals.currentUser.ssoUserInfo.hd}}` |
| **access_token** | Access token returned by the OpenID provider. | `{{globals.currentUser.ssoUserInfo.access_token}}` |
| **id_token** | ID token returned by the OpenID provider. | `{{globals.currentUser.ssoUserInfo.id_token}}` |
| **id_token_encrpted** | It is the JSON value of encrypted `id_token` | `{{globals.currentUser.ssoUserInfo.id_token_encrpted}}` |
<div style={{textAlign: 'center'}}>

View file

@ -4,21 +4,19 @@ title: Timeline
---
# Timeline
Timeline widget can be used to do a visual representation of a sequence of events
The Timeline component can be used to do a visual representation of a sequence of events.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/widgets/timeline/timeline.png" alt="ToolJet - Widget Reference - Timeline" />
<img className="screenshot-full" src="/img/widgets/timeline/timeline.png" alt="ToolJet - Widget Reference - Timeline" />
</div>
## Properties
### Timeline data
**Data requirements:** The data needs to be an array of objects and each object should have `title`, `subTitle`, `iconBackgroundColor` and `date` keys.
**Data requirements:** The data needs to be an array of objects and each object should have `title`, `subTitle`, `iconBackgroundColor` and `date` keys. The `iconBackgroundColor` can be a hex color code or in an RGBA format.
**Example:**
**Example with hex color code:**
```json
[
{ "title": "Product Launched", "subTitle": "First version of our product released to public", "date": "20/10/2021", "iconBackgroundColor": "#4d72fa"},
@ -27,21 +25,29 @@ Timeline widget can be used to do a visual representation of a sequence of event
]
```
**Example with RGBA:**
```json
[
{ "title": "Product Launched", "subTitle": "First version of our product released to public", "date": "20/10/2021", "iconBackgroundColor": "rgba(240,17,17,0.5)"},
{ "title": "First Signup", "subTitle": "Congratulations! We got our first signup", "date": "22/10/2021", "iconBackgroundColor": "rgba(60, 179, 113,0.5)"},
{ "title": "First Payment", "subTitle": "Hurray! We got our first payment", "date": "01/11/2021", "iconBackgroundColor": "rgba(60, 179, 113,0.5)"}
]
```
### Hide date
Hide date can be used to hide the date time or Left Hand Side of the timeline widget
Hide date can be used to hide the date time of the timeline component.
## General
### Tooltip
A Tooltip is often used to specify extra information about something when the user hovers the mouse pointer over the widget.
A Tooltip is often used to specify extra information when the user hovers the mouse pointer over the component.
Under the <b>General</b> accordion, you can set the value in the string format. Now hovering over the widget will display the string as the tooltip.
Under the <b>General</b> accordion, you can set the value in the string format. Now hovering over the component will display the string as the tooltip.
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/tooltip.png" alt="ToolJet - Widget Reference - Timeline" />
<img className="screenshot-full" src="/img/tooltip.png" alt="ToolJet - Component Reference - Timeline" />
</div>
@ -56,7 +62,7 @@ Under the <b>General</b> accordion, you can set the value in the string format.
| Style | Description |
| ----------- | ----------- |
| Visibility | Toggle on or off to control the visibility of the widget. You can programmatically change its value by clicking on the `Fx` button next to it. If `{{false}}` the widget will not visible after the app is deployed. By default, it's set to `{{true}}`. |
| Visibility | Toggle on or off to control the visibility of the component. You can programmatically change its value by clicking on the `Fx` button next to it. If `{{false}}` the component will not visible after the app is deployed. By default, it's set to `{{true}}`. |
:::info
Any property having `Fx` button next to its field can be **programmatically configured**.

View file

@ -394,13 +394,13 @@ const sidebars = {
'items': [
{
'type': 'link',
'label': 'Releases',
'href': 'https://github.com/ToolJet/ToolJet/releases',
'label': 'Release Notes',
'href': 'https://app.tooljet.com/applications/tj-changelog/home',
},
{
'type': 'link',
'label': 'Roadmap',
'href': 'https://github.com/tooljet/tooljet/milestones',
'href': 'https://github.com/orgs/ToolJet/projects/15',
},
],
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

BIN
docs/static/img/sso/git/gitdev.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

BIN
docs/static/img/sso/git/queryresults.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

BIN
docs/static/img/sso/git/ssogit.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

BIN
docs/static/img/sso/git/ssogithub.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

BIN
docs/static/img/sso/git/workset.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

View file

@ -21,7 +21,7 @@ All the datasource credentials are securely encrypted using `aes-256-gcm`. The c
- **TLS**: If you are using ToolJet cloud, all connections are encrypted using TLS. We also have documentation for setting up TLS for self-hosted installations of ToolJet.
- **Audit logs**: Audit logs are available on the enterprise edition of ToolJet. Every user action is logged along with the IP addresses and user information.
- **Request logging**: All the requests to server are logged. If self-hosted, you can easily extend ToolJet to use your preferred logging service. ToolJet comes with built-in Sentry integration.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (3.129.198.40) so that your datasources are not exposed to the public.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (34.86.81.252) so that your datasources are not exposed to the public.
- **Backups**: ToolJet cloud is hosted on AWS using EKS with autoscaling and regular backups.
If you notice a security vulnerability, please let the team know by sending an email to `security@tooljet.com`.

View file

@ -21,7 +21,7 @@ All the datasource credentials are securely encrypted using `aes-256-gcm`. The c
- **TLS**: If you are using ToolJet cloud, all connections are encrypted using TLS. We also have documentation for setting up TLS for self-hosted installations of ToolJet.
- **Audit logs**: Audit logs are available on the enterprise edition of ToolJet. Every user action is logged along with the IP addresses and user information.
- **Request logging**: All the requests to server are logged. If self-hosted, you can easily extend ToolJet to use your preferred logging service. ToolJet comes with built-in Sentry integration.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (3.129.198.40) so that your datasources are not exposed to the public.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (34.86.81.252) so that your datasources are not exposed to the public.
- **Backups**: ToolJet cloud is hosted on AWS using EKS with autoscaling and regular backups.
If you notice a security vulnerability, please let the team know by sending an email to `security@tooljet.com`.

View file

@ -46,7 +46,7 @@ Follow the steps below to deploy ToolJet on Cloud run with `gcloud` CLI.
<img className="screenshot-full" src="/img/cloud-run/ingress-auth.png" alt="ingress-auth" />
</div>
4. Under containers tab, please make sure the port is set 3000 and command `npm, run, start:prod` is entered in container argument field with CPU capacity is set to 2GiB.
4. Under containers tab, please make sure the port is set to 3000 and command `npm, run, start:prod` is entered in container argument field with CPU capacity set to 2GiB:
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/cloud-run/port-and-capacity-postgrest.png" alt="port-and-capacity-postgrest.png" />

View file

@ -21,7 +21,7 @@ All the datasource credentials are securely encrypted using `aes-256-gcm`. The c
- **TLS**: If you are using ToolJet cloud, all connections are encrypted using TLS. We also have documentation for setting up TLS for self-hosted installations of ToolJet.
- **Audit logs**: Audit logs are available on the enterprise edition of ToolJet. Every user action is logged along with the IP addresses and user information.
- **Request logging**: All the requests to server are logged. If self-hosted, you can easily extend ToolJet to use your preferred logging service. ToolJet comes with built-in Sentry integration.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (3.129.198.40) so that your datasources are not exposed to the public.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (34.86.81.252) so that your datasources are not exposed to the public.
- **Backups**: ToolJet cloud is hosted on AWS using EKS with autoscaling and regular backups.
If you notice a security vulnerability, please let the team know by sending an email to `security@tooljet.com`.

View file

@ -46,7 +46,7 @@ Follow the steps below to deploy ToolJet on Cloud run with `gcloud` CLI.
<img className="screenshot-full" src="/img/cloud-run/ingress-auth.png" alt="ingress-auth" />
</div>
4. Under containers tab, please make sure the port is set 3000 and command `npm, run, start:prod` is entered in container argument field with CPU capacity is set to 2GiB.
4. Under containers tab, please make sure the port is set to 3000 and command `npm, run, start:prod` is entered in container argument field with CPU capacity set to 2GiB:
<div style={{textAlign: 'center'}}>
<img className="screenshot-full" src="/img/cloud-run/port-and-capacity-tooljet.png" alt="port-and-capacity-tooljet" />

View file

@ -21,7 +21,7 @@ All the datasource credentials are securely encrypted using `aes-256-gcm`. The c
- **TLS**: If you are using ToolJet cloud, all connections are encrypted using TLS. We also have documentation for setting up TLS for self-hosted installations of ToolJet.
- **Audit logs**: Audit logs are available on the enterprise edition of ToolJet. Every user action is logged along with the IP addresses and user information.
- **Request logging**: All the requests to server are logged. If self-hosted, you can easily extend ToolJet to use your preferred logging service. ToolJet comes with built-in Sentry integration.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (3.129.198.40) so that your datasources are not exposed to the public.
- **Whitelisted IPs**: If you are using ToolJet cloud, you can whitelist our IP address (34.86.81.252) so that your datasources are not exposed to the public.
- **Backups**: ToolJet cloud is hosted on AWS using EKS with autoscaling and regular backups.
If you notice a security vulnerability, please let the team know by sending an email to `security@tooljet.com`.

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