Merge branch 'develop' into bugfix/signup-email-case-sensitive-issue

This commit is contained in:
Muhsin Shah 2022-06-22 10:52:32 +05:30
commit eafd27410f
155 changed files with 7817 additions and 924 deletions

View file

@ -1 +1 @@
1.17.1
1.19.0

View file

@ -8,7 +8,7 @@ We love your input! We want to make contributing to this project as easy and tra
## Setup
[Mac OS](https://docs.tooljet.io/docs/contributing-guide/setup/Mac%20OS)
[Mac OS](https://docs.tooljet.io/docs/contributing-guide/setup/macos)
[Docker](https://docs.tooljet.io/docs/contributing-guide/setup/docker)
[Ubuntu](https://docs.tooljet.io/docs/contributing-guide/setup/ubuntu)

View file

@ -11,7 +11,7 @@ $ npm install -g @tooljet/cli
$ tooljet COMMAND
running command...
$ tooljet (--version)
@tooljet/cli/0.0.10 darwin-arm64 node-v16.13.1
@tooljet/cli/0.0.12 darwin-arm64 node-v14.17.3
$ tooljet --help [COMMAND]
USAGE
$ tooljet COMMAND
@ -23,13 +23,26 @@ Command should be executed inside `Tooljet` directory
# Commands
<!-- commands -->
* [`tooljet info`](#tooljet-info)
* [`tooljet plugin create PLUGIN_NAME`](#tooljet-plugin-create-plugin_name)
* [`tooljet plugin delete PLUGIN_NAME`](#tooljet-plugin-delete-plugin_name)
* [`tooljet plugin install NPM_MODULE`](#tooljet-plugin-install-npm_module)
## `tooljet info`
This command returns the information about where tooljet is being run
```
USAGE
$ tooljet info
DESCRIPTION
This command returns the information about where tooljet is being run
```
## `tooljet plugin create PLUGIN_NAME`
Create a new tooljet plugin
Creates a new tooljet plugin
```
USAGE
@ -51,7 +64,7 @@ EXAMPLES
## `tooljet plugin delete PLUGIN_NAME`
Delete a tooljet plugin
Deletes a tooljet plugin
```
USAGE
@ -64,7 +77,7 @@ FLAGS
-b, --build
DESCRIPTION
Delete a tooljet plugin
Deletes a tooljet plugin
EXAMPLES
$ tooljet plugin delete <name> [--build]

4
cli/package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "@tooljet/cli",
"version": "0.0.10",
"version": "0.0.12",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@tooljet/cli",
"version": "0.0.10",
"version": "0.0.12",
"license": "MIT",
"dependencies": {
"@oclif/core": "^1.6.0",

View file

@ -1,7 +1,7 @@
{
"name": "@tooljet/cli",
"description": "tooljet cli tool",
"version": "0.0.10",
"version": "0.0.12",
"bin": {
"tooljet": "./bin/run"
},

38
cli/src/commands/info.ts Normal file
View file

@ -0,0 +1,38 @@
import { Command } from '@oclif/core';
import * as chalk from 'chalk';
import * as os from 'os';
import * as childProcess from 'child_process';
function getPackageVersion() {
try {
return require(`./package.json`).version;
} catch {
return 'N/A';
}
}
function getBinaryVersion(binaryName: string) {
try {
return childProcess.execSync(`${binaryName} --version`).toString().trim();
} catch {
return 'N/A';
}
}
export class InfoCommand extends Command {
static description = 'This command returns the information about where tooljet is being run';
async run() {
console.log(`
Operating System:
platform: ${chalk.green(os.platform())}
arch: ${chalk.green(os.arch())}
version: ${chalk.green(os.version())}
Binaries:
node: ${chalk.green(process.versions.node)}
npm: ${chalk.green(getBinaryVersion('npm'))}
Relevant packages:
tooljet: ${chalk.green(getPackageVersion())}
`);
}
}

View file

@ -1,117 +1,118 @@
export const buttonSelector={
buttonWidget: "[data-cy=button-widget]",
buttonProperties: "[data-rb-event-key=properties]",
buttonStyles: "[data-rb-event-key=styles]",
buttonName: "[data-cy=edit-widget-name]",
propertiesElements:{
propertiesAccordion: "[data-cy=widget-accordion]:eq(0)",
buttonTextLabel:"[data-cy=accordion-components]:eq(0)",
buttonTextInput:"[data-cy=accordion-input] :nth-child(5)",
loadingState: "[data-cy=accordion-components]:eq(1)",
eventsAccordion: "[data-cy=widget-accordion]:eq(1)",
addEventListner:"[data-cy=add-event-handler]",
noEventHandler:"[data-cy=no-event-handler-message]",
layoutAccordion: "[data-cy=widget-accordion]:eq(2)",
showOnDesktopLabel: "[data-cy=accordion-components]:eq(2)",
showOnMobileLabel: "[data-cy=accordion-components]:eq(3)",
documentationLink: "[data-cy=widget-documentation-link]",
},
loadingStateFx: "[data-cy=fx-button]:eq(2)",
loadingStateToggle: "[data-cy=toggle-button]:eq(0)",
desktopFx:"[data-cy=fx-button]:eq(4)",
desktopToggle:"[data-cy=toggle-button]:eq(1)",
mobileFx: "[data-cy=fx-button]:eq(6)",
mobileToggle: "[data-cy=toggle-button]:eq(2)",
buttonInputField: "[data-cy=accordion-input] > .CodeMirror > .CodeMirror-scroll > .CodeMirror-sizer",
fxLoadingState:".cm-atom:eq(0)",
loadingStateInputFx: "[data-cy=fx-button]:eq(1)",
eventHandler: "[data-cy=event-handler]",
popoverCard: "[data-cy=popover-card]",
eventLabel: "[data-cy=event-label]",
eventSelection: "[data-cy=event-selection] > .select-search > .select-search__value > .select-search__input",
actionLabel: "[data-cy=action-label]",
actionSelection: "[data-cy=action-selection] ",
actionOption: "[data-cy=action-option]",
messageLabel: "[data-cy=message-label]",
messageText: "[data-cy=accordion-input] > .CodeMirror > .CodeMirror-scroll > .CodeMirror-sizer >:eq(10)",
alertTypeLabel: "[data-cy=alert-type-label]",
alertMessageType: "[data-cy=alert-message-type]",
fxDesktop:".cm-atom:eq(2)",
desktopInputFx: "[data-cy=fx-button]:eq(3)",
mobileInputFx: "[data-cy=fx-button]:eq(5)",
fxMobile: ".cm-atom:eq(4)",
stylesElements: {
backgroundColorLabel: "[data-cy=accordion-components]:eq(4)",
textColorLabel: "[data-cy=accordion-components]:eq(5)",
loaderColorLabel: "[data-cy=accordion-components]:eq(6)",
visibilityLabel: "[data-cy=accordion-components]:eq(7)",
disableLabel: "[data-cy=accordion-components]:eq(8)",
borderRadiusLabel: "[data-cy=accordion-components]:eq(9)",
},
stylesInput: {
backgroundColorInputField: "[data-cy=color-picker-input]:eq(0)",
textColorInputField: "[data-cy=color-picker-input]:eq(1)",
loaderColorInputField: "[data-cy=color-picker-input]:eq(2)",
borderRadiusInputField: "[data-cy=border-radius-input]",
},
stylesFx:{
backgroundColor: "[data-cy=fx-button]:eq(8)",
textColor: "[data-cy=fx-button]:eq(10)",
loaderColor: "[data-cy=fx-button]:eq(12)",
visibility: "[data-cy=fx-button]:eq(14)",
disable: "[data-cy=fx-button]:eq(16)",
borderRadius: "[data-cy=fx-button]:eq(18)",
},
backgroundColorSelector:"[data-cy=color-picker-input] > .col-auto:eq(0)",
colorPickCard: ".sketch-picker",
hexLabel:'[style="-webkit-box-flex: 2; flex: 2 1 0%;"] > div > label',
backgroundColorInput: "[data-cy=accordion-input] > .CodeMirror >> .CodeMirror-sizer .CodeMirror-lines >> .CodeMirror-code >> span:eq(1)",
backgroundColorCloseFx:"[data-cy=fx-button]:eq(7)",
backgroundColor:{
inputHex: "#rc-editable-input-1",
inputR: "#rc-editable-input-2",
labelR: ":nth-child(2) > div > label",
inputG: "#rc-editable-input-3",
labelG:":nth-child(3) > div > label",
inputB: "#rc-editable-input-4",
labelB: ":nth-child(4) > div > label",
inputA: "#rc-editable-input-5",
labelA: ":nth-child(5) > div > label",
},
textColorSelector: "[data-cy=color-picker-input] > .col-auto:eq(1)",
textColorInput: "[data-cy=accordion-input] > .CodeMirror >> .CodeMirror-sizer .CodeMirror-lines >> .CodeMirror-code >> span:eq(2)",
textColorCloseFx: "[data-cy=fx-button]:eq(9)",
textColor:{
inputHex: "#rc-editable-input-6",
inputR: "#rc-editable-input-7",
labelR: ":nth-child(2) > div > label",
inputG: "#rc-editable-input-8",
labelG:":nth-child(3) > div > label",
inputB: "#rc-editable-input-9",
labelB: ":nth-child(4) > div > label",
inputA: "#rc-editable-input-10",
labelA: ":nth-child(5) > div > label",
},
loaderColorSelector: "[data-cy=color-picker-input] > .col-auto:eq(2)",
loaderColorInput: "[data-cy=accordion-input] > .CodeMirror >> .CodeMirror-sizer .CodeMirror-lines >> .CodeMirror-code >> span:eq(3)",
loaderColorCloseFx: "[data-cy=fx-button]:eq(11)",
loaderColor: {
inputHex: "#rc-editable-input-11",
inputR: "#rc-editable-input-12",
labelR: ":nth-child(2) > div > label",
inputG: "#rc-editable-input-13",
labelG:":nth-child(3) > div > label",
inputB: "#rc-editable-input-14",
labelB: ":nth-child(4) > div > label",
inputA: "#rc-editable-input-15",
labelA: ":nth-child(5) > div > label",
},
fxVisibility: ".cm-atom:eq(0)",
fxDisable: ".cm-atom:eq(2)",
visibilityToggle: "[data-cy=toggle-button]:eq(3)",
disableToggle: "[data-cy=toggle-button]:eq(4)",
visibilityCloseFx: "[data-cy=fx-button]:eq(13)",
disableCloseFx: "[data-cy=fx-button]:eq(15)",
borderRadiusCloseFx: "[data-cy=fx-button]:eq(15)",
fxBorderRadius: ".CodeMirror-code > .CodeMirror-line >> .cm-number"
buttonWidget: "[data-cy=button-widget]",
buttonProperties: "[data-rb-event-key=properties]",
buttonStyles: "[data-rb-event-key=styles]",
buttonName: "[data-cy=edit-widget-name]",
propertiesElements:{
propertiesAccordion: "[data-cy=widget-accordion]:eq(0)",
buttonTextLabel:"[data-cy=accordion-components]:eq(0)",
buttonTextInput: '#collapse-0 > .accordion-body > :nth-child(1) > :nth-child(1) > .row > .col > .code-hinter-wrapper > [data-cy=accordion-input] > .CodeMirror >> .CodeMirror-sizer >>.CodeMirror-lines >> .CodeMirror-code >> span',
loadingState: "[data-cy=accordion-components]:eq(1)",
eventsAccordion: "[data-cy=widget-accordion]:eq(1)",
addEventListner: "[data-cy=add-event-handler]",
noEventHandler: "[data-cy=no-event-handler-message]",
generalAccordion: "[data-cy=widget-accordion]:eq(2)",
layoutAccordion: "[data-cy=widget-accordion]:eq(3)",
showOnDesktopLabel: "[data-cy=accordion-components]:eq(3)",
showOnMobileLabel: "[data-cy=accordion-components]:eq(4)",
documentationLink: "[data-cy=widget-documentation-link]",
},
loadingStateFx: "[data-cy=fx-button]:eq(3)",
loadingStateToggle: "[data-cy=toggle-button]:eq(0)",
desktopFx:"[data-cy=fx-button]:eq(6)",
desktopToggle:"[data-cy=toggle-button]:eq(1)",
mobileFx: "[data-cy=fx-button]:eq(8)",
mobileToggle: "[data-cy=toggle-button]:eq(2)",
buttonInputField: "[data-cy=accordion-input] > .CodeMirror > .CodeMirror-scroll > .CodeMirror-sizer",
fxLoadingState:".cm-atom:eq(0)",
loadingStateInputFx: "[data-cy=fx-button]:eq(2)",
eventHandler: "[data-cy=event-handler]",
popoverCard: "[data-cy=popover-card]",
eventLabel: "[data-cy=event-label]",
eventSelection: "[data-cy=event-selection] > .select-search > .select-search__value > .select-search__input",
actionLabel: "[data-cy=action-label]",
actionSelection: "[data-cy=action-selection] ",
actionOption: "[data-cy=action-option]",
messageLabel: "[data-cy=message-label]",
messageText: "[data-cy=accordion-input] > .CodeMirror > .CodeMirror-scroll > .CodeMirror-sizer >:eq(11)",
alertTypeLabel: "[data-cy=alert-type-label]",
alertMessageType: "[data-cy=alert-message-type]",
fxDesktop:".cm-atom:eq(2)",
desktopInputFx: "[data-cy=fx-button]:eq(5)",
mobileInputFx: "[data-cy=fx-button]:eq(7)",
fxMobile: ".cm-atom:eq(4)",
stylesElements: {
backgroundColorLabel: "[data-cy=accordion-components]:eq(5)",
textColorLabel: "[data-cy=accordion-components]:eq(6)",
loaderColorLabel: "[data-cy=accordion-components]:eq(7)",
visibilityLabel: "[data-cy=accordion-components]:eq(8)",
disableLabel: "[data-cy=accordion-components]:eq(9)",
borderRadiusLabel: "[data-cy=accordion-components]:eq(10)",
},
stylesInput: {
backgroundColorInputField: "[data-cy=color-picker-input]:eq(0)",
textColorInputField: "[data-cy=color-picker-input]:eq(1)",
loaderColorInputField: "[data-cy=color-picker-input]:eq(2)",
borderRadiusInputField: "[data-cy=border-radius-input]",
},
stylesFx:{
backgroundColor: "[data-cy=fx-button]:eq(10)",
textColor: "[data-cy=fx-button]:eq(12)",
loaderColor: "[data-cy=fx-button]:eq(14)",
visibility: "[data-cy=fx-button]:eq(16)",
disable: "[data-cy=fx-button]:eq(18)",
borderRadius: "[data-cy=fx-button]:eq(20)",
},
backgroundColorSelector:"[data-cy=color-picker-input] > .col-auto:eq(0)",
colorPickCard: ".sketch-picker",
hexLabel:'[style="-webkit-box-flex: 2; flex: 2 1 0%;"] > div > label',
backgroundColorInput: "[data-cy=accordion-input] > .CodeMirror >> .CodeMirror-sizer .CodeMirror-lines >> .CodeMirror-code >> span:eq(1)",
backgroundColorCloseFx:"[data-cy=fx-button]:eq(9)",
backgroundColor:{
inputHex: "#rc-editable-input-1",
inputR: "#rc-editable-input-2",
labelR: ":nth-child(2) > div > label",
inputG: "#rc-editable-input-3",
labelG:":nth-child(3) > div > label",
inputB: "#rc-editable-input-4",
labelB: ":nth-child(4) > div > label",
inputA: "#rc-editable-input-5",
labelA: ":nth-child(5) > div > label",
},
textColorSelector: "[data-cy=color-picker-input] > .col-auto:eq(1)",
textColorInput: "[data-cy=accordion-input] > .CodeMirror >> .CodeMirror-sizer .CodeMirror-lines >> .CodeMirror-code >> span:eq(2)",
textColorCloseFx: "[data-cy=fx-button]:eq(11)",
textColor:{
inputHex: "#rc-editable-input-6",
inputR: "#rc-editable-input-7",
labelR: ":nth-child(2) > div > label",
inputG: "#rc-editable-input-8",
labelG:":nth-child(3) > div > label",
inputB: "#rc-editable-input-9",
labelB: ":nth-child(4) > div > label",
inputA: "#rc-editable-input-10",
labelA: ":nth-child(5) > div > label",
},
loaderColorSelector: "[data-cy=color-picker-input] > .col-auto:eq(2)",
loaderColorInput: "[data-cy=accordion-input] > .CodeMirror >> .CodeMirror-sizer .CodeMirror-lines >> .CodeMirror-code >> span:eq(3)",
loaderColorCloseFx: "[data-cy=fx-button]:eq(13)",
loaderColor: {
inputHex: "#rc-editable-input-11",
inputR: "#rc-editable-input-12",
labelR: ":nth-child(2) > div > label",
inputG: "#rc-editable-input-13",
labelG:":nth-child(3) > div > label",
inputB: "#rc-editable-input-14",
labelB: ":nth-child(4) > div > label",
inputA: "#rc-editable-input-15",
labelA: ":nth-child(5) > div > label",
},
fxVisibility: ".cm-atom:eq(0)",
fxDisable: ".cm-atom:eq(2)",
visibilityToggle: "[data-cy=toggle-button]:eq(3)",
disableToggle: "[data-cy=toggle-button]:eq(4)",
visibilityCloseFx: "[data-cy=fx-button]:eq(15)",
disableCloseFx: "[data-cy=fx-button]:eq(17)",
borderRadiusCloseFx: "[data-cy=fx-button]:eq(15)",
fxBorderRadius: ".CodeMirror-code > .CodeMirror-line >> .cm-number"
};

View file

@ -6,6 +6,7 @@ export const commonSelectors={
firstWidget:"[data-cy=widget-list]:eq(0)",
canvas:"[data-cy=real-canvas]",
appCardOptions: "[data-cy=app-card-menu-icon]",
folderItemOptions: "[data-cy=folder-item-menu-icon]",
deleteApp: "[data-cy=card-options] :nth-child(5)>span",
confirmButton: "[data-cy=confirm-yes-button]",
autoSave: "[data-cy=autosave-indicator]",
@ -15,4 +16,11 @@ export const commonSelectors={
emailField: "[data-cy=email-text-field]",
passwordField: "[data-cy=password-text-field]",
signInButton: "[data-cy=login-button]",
dropdown: "[data-cy=workspace-dropdown]",
backButton: "[data-cy=back-button]",
emptyAppCreateButton: "[data-cy=create-new-application]",
appCreateButton: "[data-cy=create-new-app-button]",
createButton: "[data-cy=create-button]",
appNameInput: "[data-cy=app-name-input]",
dropdown: "[data-cy=workspace-dropdown]",
}

View file

@ -0,0 +1,32 @@
export const groupsSelector= {
pageTitle: "[data-cy=user-groups-title]",
createNewGroupButton: "[data-cy=create-new-group-button]",
tableHeader: "[data-cy=table-header]",
groupName:"[data-cy=group-name]",
cardTitle: "[data-cy=card-title]",
groupNameInput: "[data-cy=group-name-input]",
cancelButton: "[data-cy=cancel-button]",
createGroupButton: "[data-cy=create-group-button]",
userGroup: "[data-cy=user-groups]",
groupName: "[data-cy=group-name]",
appsLink: "[data-cy=apps-link]",
usersLink: "[data-cy=users-link]",
permissionsLink: "[data-cy=permissions-link]",
searchBox: ".select-search__input",
addButton: ".active > .row > .col-auto",
nameTableHeader: ".active [data-cy=name-header]",
permissionstableHedaer: ".active [data-cy=permissions-header]",
emailTableHeader: "[data-cy=email-header]",
resourcesTableHeader: "[data-cy=resource-header]",
resourcesApps: "[data-cy=resource-apps]",
resourcesFolders: "[data-cy=resource-folders]",
appsCreateCheck: "[data-cy=app-create-checkbox]",
appsCreateLabel: "[data-cy=app-create-label]",
appsDeleteCheck: "[data-cy=app-delete-checkbox]",
appsDeleteLabel: "[data-cy=app-delete-label]",
foldersCreateCheck: "[data-cy=folder-create-checkbox]",
foldersCreateLabel: "[data-cy=folder-create-label]",
confirmText: "[data-cy=modal-message]",
confirmCancelButton: "[data-cy=confirm-cancel-button]",
confirmYesButton : "[data-cy=confirm-yes-button]",
};

View file

@ -0,0 +1,29 @@
export const ssoSelector ={
pagetitle: "[data-cy=manage-sso-page-title]",
generalSettings: "[data-cy=left-menu-items] :eq(0)",
cardTitle: "[data-cy=card-title]",
enableCheckbox: "[data-cy=form-check-input]",
enableSignupLabel: "[data-cy=form-check-label]",
helperText: "[data-cy=general-settings-help-text]",
domainLabel: "[data-cy=allowed-domains-label]",
domainInput: "[data-cy=allowed-domain-input]",
cancelButton: "[data-cy=cancel-button]",
saveButton: "[data-cy=save-button]",
google: "[data-cy=left-menu-items] :eq(1)",
statusLabel: "[data-cy=status-label]",
clientIdLabel: "[data-cy=client-id-label]",
clientIdInput: "[data-cy=client-id-input]",
redirectUrlLabel: "[data-cy=redirect-url-label]",
redirectUrl: "[data-cy=redirect-url]",
googleTile: "[data-cy=google-sign-in-tile]",
googleIcon: "[data-cy=google-icon]",
googleSignInText: "[data-cy=google-sign-in-text]",
git: "[data-cy=left-menu-items] :eq(2)",
clientSecretLabel: "[data-cy=client-secret-label]",
encriptedLabel: "[data-cy=encripted-label]",
clientSecretInput: "[data-cy=client-secret-input]",
gitTile: "[data-cy=git-tile]",
gitIcon: "[data-cy=git-icon]",
gitSignInText: "[data-cy=git-sign-in-text]",
password: "[data-cy=left-menu-items] :eq(3)",
};

View file

@ -29,7 +29,7 @@ export const usersSelector ={
firstNameLabel: "[data-cy=first-name-label]",
lastNameLabel:"[data-cy=last-name-label]",
companyLabel: "[data-cy=company-label]",
roleLable: "[data-cy=role-label]",
roleLabel: "[data-cy=role-label]",
passwordLabel: "[data-cy=password-label]",
confirmpasswordLabel: "[data-cy=confirm-password-label]",
termsInfo: "[data-cy=terms-and-condition-info]",
@ -47,4 +47,11 @@ export const usersSelector ={
createNewApp: "[data-cy=create-new-application]",
dropdownText: "[data-cy=dropdown-organization-list]>>:eq(0)",
arrowIcon: "[data-cy=workspace-arrow-icon]",
singleWorkspaceElements:{
cardTitle: "[data-cy=card-title]",
passwordLabel: "[data-cy=password-label]",
confirmpasswordLabel: "[data-cy=confirm-password-label]",
termsInfo: "[data-cy=terms-and-condition-info]",
}
}

View file

@ -1,46 +1,47 @@
export const buttonText={
widgetName: "Button",
buttonProperties: "Properties",
buttonStyles: "Styles",
buttonName: "button1",
propertiesElements:{
propertiesAccordion: "Properties",
buttonTextLabel: "Button Text",
buttonTextInput: "Button",
loadingState: "Loading State",
eventsAccordion: "Events",
addEventListner: "+ Add event handler",
noEventHandler: "This button doesn't have any event handlers",
layoutAccordion: "Layout",
showOnDesktopLabel: "Show on desktop",
showOnMobileLabel: "Show on mobile",
documentationLink: "Button documentation",
},
fxButton:"Fx",
savedToast: "Saved!",
invalidButtonName: "New Button",
buttonNameErrToast: "Invalid widget name. Should be unique and only include letters, numbers and underscore.",
validButtonName: "button",
buttonText:"Send",
trueText: "true",
falseText:"false",
eventLabel: "Event",
actionLabel: "Action",
actionOption: "Action options",
messageLabel: "Message",
alertTypeLabel: "Alert Type",
newMessage: " new toast",
newToast: "Hello world new toast",
stylesElements:{
backgroundColorLabel: "Background color",
textColorLabel: "Text color",
loaderColorLabel: "Loader color",
visibilityLabel: "Visibility",
disableLabel: "Disable",
borderRadiusLabel: "Border radius",
},
backgroundColorInput: "#375FCF",
textColorInput: "#fff",
loaderColorInput: "#fff",
borderRadiusInput: "15",
widgetName: "Button",
buttonProperties: "Properties",
buttonStyles: "Styles",
buttonName: "button1",
propertiesElements:{
propertiesAccordion: "Properties",
buttonTextLabel: "Button Text",
buttonTextInput: "Button",
loadingState: "Loading State",
eventsAccordion: "Events",
addEventListner: "+ Add event handler",
noEventHandler: "This button doesn't have any event handlers",
generalAccordion: "General",
layoutAccordion: "Layout",
showOnDesktopLabel: "Show on desktop",
showOnMobileLabel: "Show on mobile",
documentationLink: "Button documentation",
},
fxButton:"Fx",
savedToast: "Saved!",
invalidButtonName: "New Button",
buttonNameErrToast: "Invalid widget name. Should be unique and only include letters, numbers and underscore.",
validButtonName: "button",
buttonText:"Send",
trueText: "true",
falseText:"false",
eventLabel: "Event",
actionLabel: "Action",
actionOption: "Action options",
messageLabel: "Message",
alertTypeLabel: "Alert Type",
newMessage: " new toast",
newToast: "Hello world new toast",
stylesElements:{
backgroundColorLabel: "Background color",
textColorLabel: "Text color",
loaderColorLabel: "Loader color",
visibilityLabel: "Visibility",
disableLabel: "Disable",
borderRadiusLabel: "Border radius",
},
backgroundColorInput: "#375FCF",
textColorInput: "#fff",
loaderColorInput: "#fff",
borderRadiusInput: "15",
};

View file

@ -1,9 +1,10 @@
export const path={
loginPath:"/login",
loginPath: "/login",
profilePath: "/settings",
manageUsers: "/users",
confirmInvite: "/confirm-invite"
confirmInvite: "/confirm-invite",
manageGroups: "/groups",
manageSSO: "/manage-sso",
}
export const commonText={

View file

@ -0,0 +1,31 @@
export const groupsText= {
pageTitle: "User Groups",
createNewGroupButton: "Create new group",
tableHeader: "Name",
allUsers: "All Users",
admin: "Admin",
cardTitle: "Add new group",
cancelButton: "Cancel",
createGroupButton: "Create Group",
groupNameExistToast: "Group name already exist",
testGroup: "Test",
groupCreatedToast: "Group has been created",
userGroup: "User groups",
appsLink: "Apps",
usersLink: "Users",
permissionsLink: "Permissions",
addButton: "Add",
nameTableHeader: "Name",
permissionstableHedaer: "Permissions",
emailTableHeader: "Email",
resourcesTableHeader: "Resources",
resourcesApps: "Apps",
resourcesFolders: "Folders",
createLabel: "Create",
folderCreateLabel: "Create/Update/Delete",
deleteLabel: "Delete",
permissionUpdatedToast: "Group permissions updated",
confirmText: "This group will be permanently deleted. Do you want to continue?",
confirmCancelButton: "Cancel",
confirmYesButton : "Yes",
};

View file

@ -0,0 +1,31 @@
export const ssoText ={
pagetitle: "Manage SSO",
generalSettings: "General Settings",
enableSignupLabel: "Enable signup",
helperText: "New account will be created for user's first time SSO sign in",
domainLabel: "Allowed domains",
cancelButton: "Cancel",
saveButton: "Save",
allowedDomain: "tooljet.io,gmail.com",
ssoToast: "updated sso configurations",
ssoToast2: "updated SSO configurations",
googleTitle: "Google",
enabledLabel: "Enabled",
googleEnabledToast: "Enabled Google SSO",
disabledLabel: "Disabled",
googleDisableToast: "Disabled Google SSO",
googleSignInText: "Sign in with Google",
clientIdLabel: "Client Id",
redirectUrlLabel: "Redirect URL",
clientId: "24567098-mklj8t20za1smb2if.apps.googleusercontent.com",
testClientId: "12345-client-id-.apps.googleusercontent.com",
gitTitle: "GitHub",
clientSecretLabel: "Client Secret",
encriptedLabel: "Encrypted",
gitEnabledToast: "Enabled GitHub SSO",
gitDisabledToast: "Disabled GitHub SSO",
gitSignInText: "Sign in with Github",
passwordTitle: "Password Login",
passwordEnabledToast: "Enabled Form login",
passwordDisabledToast: "Disabled Form login",
};

View file

@ -40,5 +40,13 @@ export const usersText = {
archivedStatus: "archived",
invitedStatus: "invited",
archivedToast: "The user has been archived",
inviteToast: "Added to the workspace successfully."
inviteToast: "Added to the workspace successfully.",
singleWorkspaceElements:{
cardTitle: "Set up your account",
passwordLabel: "Password",
confirmpasswordLabel: "Confirm Password",
termsInfo: "By clicking the button below, you agree to our Terms and Conditions.",
},
swPasswordSuccessToast: "Added to the workspace and password has been set successfully."
}

View file

@ -0,0 +1,70 @@
import {commonSelectors} from "Selectors/common";
import { groupsSelector } from "Selectors/manageGroups";
import { groupsText } from "Texts/manageGroups";
import { fake } from "Fixtures/fake";
import * as common from "Support/utils/common";
import * as groups from "Support/utils/manageGroups";
const groupName = fake.firstName.replaceAll("[^A-Za-z]", "");
describe("Manage Groups", ()=>{
before(()=>{
cy.appUILogin();
});
it("Should verify the elements and functionalities on manage groups page",()=>{
common.navigateToManageGroups();
groups.manageGroupsElements();
cy.get(groupsSelector.createNewGroupButton).click();
cy.clearAndType(groupsSelector.groupNameInput, groupsText.admin);
cy.get(groupsSelector.createGroupButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.groupNameExistToast);
cy.get(groupsSelector.cancelButton).click();
cy.get(groupsSelector.tableHeader).should("be.visible").and("have.text", groupsText.tableHeader);
cy.wait(2000);
cy.get(groupsSelector.createNewGroupButton).click();
cy.clearAndType(groupsSelector.groupNameInput, groupName);
cy.get(groupsSelector.createGroupButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.groupCreatedToast);
cy.get(groupsSelector.groupName).contains(groupName).click();
cy.get(groupsSelector.userGroup).should("be.visible").and("have.text", groupsText.userGroup);
cy.get(groupsSelector.groupName).should("be.visible").and("have.text", groupName);
cy.get(groupsSelector.searchBox).should("be.visible");
cy.get(groupsSelector.addButton).should("be.visible").and("have.text", groupsText.addButton);
cy.get(groupsSelector.nameTableHeader).should("be.visible").and("have.text", groupsText.nameTableHeader);
cy.get(groupsSelector.permissionstableHedaer).should("be.visible").and("have.text", groupsText.permissionstableHedaer);
cy.get(groupsSelector.usersLink).click();
cy.get(groupsSelector.searchBox).should("be.visible");
cy.get(groupsSelector.addButton).should("be.visible").and("have.text", groupsText.addButton);
cy.get(groupsSelector.nameTableHeader).should("be.visible").and("have.text", groupsText.nameTableHeader);
cy.get(groupsSelector.emailTableHeader).should("be.visible").and("have.text", groupsText.emailTableHeader);
cy.get(groupsSelector.permissionsLink).click();
cy.get(groupsSelector.resourcesApps).should("be.visible").and("have.text", groupsText.resourcesApps);
cy.get(groupsSelector.permissionstableHedaer).should("be.visible").and("have.text", groupsText.permissionstableHedaer);
cy.get(groupsSelector.resourcesApps).should("be.visible").and("have.text", groupsText.resourcesApps);
cy.get(groupsSelector.appsCreateCheck).should("be.visible").check();
cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.permissionUpdatedToast);
cy.get(groupsSelector.appsCreateLabel).should("be.visible").and("have.text", groupsText.createLabel);
cy.get(groupsSelector.appsCreateCheck).uncheck();
cy.get(groupsSelector.appsDeleteCheck).should("be.visible").check();
cy.get(groupsSelector.appsDeleteLabel).should("be.visible").and("have.text", groupsText.deleteLabel);
cy.get(groupsSelector.appsDeleteCheck).uncheck();
cy.get(groupsSelector.userGroup).click();
cy.contains('td', groupName).parent().within(() => {
cy.get('td a').contains("Delete").click();
});
cy.get(groupsSelector.confirmText).should("be.visible").and("have.text", groupsText.confirmText);
cy.get(groupsSelector.confirmCancelButton).should("be.visible").and("have.text", groupsText.confirmCancelButton);
cy.get(groupsSelector.confirmYesButton).should("be.visible").and("have.text", groupsText.confirmYesButton);
cy.get(groupsSelector.confirmCancelButton).click();
cy.contains('td', groupName).parent().within(() => {
cy.get('td a').contains("Delete").click();
});
cy.get(groupsSelector.confirmYesButton).click();
});
});

View file

@ -0,0 +1,86 @@
import { ssoSelector } from "Selectors/manageSSO";
import * as common from "Support/utils/common";
import { ssoText } from "Texts/manageSSO";
import * as SSO from "Support/utils/manageSSO";
describe("Manage SSO for single workspace", ()=>{
before(()=>{
cy.appUILogin();
});
it("Should verify General settings page elements", ()=>{
common.navigateToManageSSO();
cy.get(ssoSelector.pagetitle).should("be.visible").and("have.text", ssoText.pagetitle);
cy.get(ssoSelector.generalSettings).should("be.visible").and("have.text", ssoText.generalSettings);
cy.get(ssoSelector.cardTitle).should("be.visible").and("have.text", ssoText.generalSettings);
cy.get(ssoSelector.enableCheckbox).should("be.visible");
cy.get(ssoSelector.enableSignupLabel).should("be.visible").and("have.text", ssoText.enableSignupLabel);
cy.get(ssoSelector.helperText).should("be.visible").and("have.text", ssoText.helperText );
cy.get(ssoSelector.domainLabel).should("be.visible").and("have.text", ssoText.domainLabel);
cy.get(ssoSelector.domainInput).should("be.visible");
cy.get(ssoSelector.cancelButton).should("be.visible").and("have.text", ssoText.cancelButton);
cy.get(ssoSelector.saveButton).should("be.visible").and("have.text", ssoText.saveButton);
SSO.generalSettings();
});
it("Should verify Google SSO page elements", ()=>{
cy.wait(1000);
cy.get(ssoSelector.google).should("be.visible").click();
cy.get(ssoSelector.cardTitle).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(ssoText.googleTitle);}).and(("be.visible"));
cy.get(ssoSelector.enableCheckbox).should("be.visible");
cy.get(ssoSelector.clientIdLabel).should("be.visible").and("have.text", ssoText.clientIdLabel);
cy.get(ssoSelector.clientIdInput).should("be.visible");
cy.get(ssoSelector.cancelButton).should("be.visible").and("have.text", ssoText.cancelButton);
cy.get(ssoSelector.saveButton).should("be.visible").and("have.text", ssoText.saveButton);
SSO.googleSSO();
cy.get(ssoSelector.redirectUrlLabel).should("be.visible").and("have.text", ssoText.redirectUrlLabel);
cy.get(ssoSelector.redirectUrl).should("be.visible");
common.logout();
cy.get(ssoSelector.googleTile).should("be.visible");
cy.get(ssoSelector.googleIcon).should("be.visible");
cy.get(ssoSelector.googleSignInText).should("be.visible").and("have.text", ssoText.googleSignInText);
});
it("Should verify Git SSO page elements", ()=>{
cy.appUILogin();
common.navigateToManageSSO();
cy.get(ssoSelector.git).should("be.visible").click();
cy.get(ssoSelector.cardTitle).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(ssoText.gitTitle);}).and(("be.visible"));
cy.get(ssoSelector.enableCheckbox).should("be.visible");
cy.get(ssoSelector.clientIdLabel).should("be.visible").and("have.text", ssoText.clientIdLabel);
cy.get(ssoSelector.clientIdInput).should("be.visible");
cy.get(ssoSelector.clientSecretLabel).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(ssoText.clientSecretLabel);}).and(("be.visible"));
cy.get(ssoSelector.encriptedLabel).should("be.visible").and("have.text", ssoText.encriptedLabel);
cy.get(ssoSelector.clientSecretInput).should("be.visible");
cy.get(ssoSelector.cancelButton).should("be.visible").and("have.text", ssoText.cancelButton);
cy.get(ssoSelector.saveButton).should("be.visible").and("have.text", ssoText.saveButton);
SSO.gitSSO();
cy.get(ssoSelector.redirectUrlLabel).should("be.visible").and("have.text", ssoText.redirectUrlLabel);
cy.get(ssoSelector.redirectUrl).should("be.visible");
common.logout();
cy.get(ssoSelector.gitTile).should("be.visible");
cy.get(ssoSelector.gitIcon).should("be.visible");
cy.get(ssoSelector.gitSignInText).should("be.visible").and("have.text", ssoText.gitSignInText);
});
it("Should verify Password login page elements",()=>{
cy.appUILogin();
common.navigateToManageSSO();
cy.get(ssoSelector.password).should("be.visible").click();
cy.get(ssoSelector.cardTitle).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(ssoText.passwordTitle);}).and(("be.visible"));
cy.get(ssoSelector.enableCheckbox).should("be.visible");
SSO.password();
})
});

View file

@ -0,0 +1,185 @@
import {commonSelectors} from "Selectors/common";
import { fake } from "Fixtures/fake";
import { usersSelector } from "Selectors/manageUsers";
import { usersText } from "Texts/manageUsers";
import * as users from "Support/utils/manageUsers";
import * as common from "Support/utils/common";
import { path } from "Texts/common";
import { commonText } from "Texts/common";
const firstName = fake.firstName;
const lastName = fake.lastName.replaceAll("[^A-Za-z]", "");
const email = (`${firstName}@example.com`).toLowerCase();
describe("Manage Users for single workspace", ()=>{
before(()=>{
cy.appUILogin();
});
it("Should verify the Manage users page", ()=>{
common.navigateToManageUsers();
users.manageUsersElements();
cy.get(usersSelector.cancelButton).click();
cy.get(usersSelector.usersElements.nameTitile).should("be.visible");
cy.get(usersSelector.inviteUserButton).click();
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.lastNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.emailError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.clearAndType(usersSelector.firstNameInput, firstName);
cy.get(usersSelector.lastNameInput).clear();
cy.get(usersSelector.emailInput).clear();
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.lastNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.emailError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.firstNameInput).clear();
cy.get(usersSelector.emailInput).clear();
cy.clearAndType(usersSelector.lastNameInput, lastName);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.emailError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.firstNameInput).clear();
cy.get(usersSelector.lastNameInput).clear();
cy.clearAndType(usersSelector.emailInput, email);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.lastNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.firstNameInput).clear();
cy.clearAndType(usersSelector.lastNameInput, lastName);
cy.clearAndType(usersSelector.emailInput, email);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.fisrtNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.lastNameInput).clear();
cy.clearAndType(usersSelector.firstNameInput, firstName);
cy.clearAndType(usersSelector.emailInput, email);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.lastNameError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.get(usersSelector.emailInput).clear();
cy.clearAndType(usersSelector.firstNameInput, firstName);
cy.clearAndType(usersSelector.lastNameInput, lastName);
cy.get(usersSelector.createUserButton).click();
cy.get(usersSelector.emailError).should("be.visible").and("have.text", usersText.fieldRequired);
cy.clearAndType(usersSelector.firstNameInput, firstName);
cy.clearAndType(usersSelector.lastNameInput, lastName);
cy.clearAndType(usersSelector.emailInput, usersText.usersElements.userEmail);
cy.get(usersSelector.createUserButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.exsitingEmail);
});
it("Should verify the confirm invite page", ()=>{
users.inviteUser(firstName,lastName,email);
cy.get(usersSelector.confirmInvitePage).should("be.visible");
cy.get(usersSelector.pageLogo).should("be.visible");
for( const element in usersSelector.singleWorkspaceElements){
cy.get(usersSelector.singleWorkspaceElements[element]).should("be.visible").and("have.text", usersText.singleWorkspaceElements[element]);
}
cy.get(usersSelector.finishSetup).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.passwordErrToast);
cy.get(usersSelector.passwordInput).should("have.value", "");
cy.get(usersSelector.confirmPasswordInput).should("have.value", "");
cy.clearAndType(usersSelector.passwordInput, usersText.password);
cy.wait(1000);
cy.get(usersSelector.finishSetup).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.passwordErrToast);
cy.get(usersSelector.passwordInput).should("have.value", usersText.password);
cy.get(usersSelector.confirmPasswordInput).should("have.value", "");
cy.get(usersSelector.passwordInput).clear();
cy.clearAndType(usersSelector.confirmPasswordInput, usersText.password);
cy.get(usersSelector.finishSetup).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.passwordErrToast);
cy.get(usersSelector.passwordInput).should("have.value", "");
cy.get(usersSelector.confirmPasswordInput).should("have.value", usersText.password);
cy.clearAndType(usersSelector.passwordInput, usersText.password);
cy.clearAndType(usersSelector.confirmPasswordInput, usersText.mismatchPassword);
cy.get(usersSelector.finishSetup).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.passwordMismatchToast);
cy.get(usersSelector.passwordInput).should("have.value", usersText.password);
cy.get(usersSelector.confirmPasswordInput).should("have.value", usersText.mismatchPassword);
cy.clearAndType(usersSelector.passwordInput, usersText.password);
cy.clearAndType(usersSelector.confirmPasswordInput, usersText.password);
cy.get(usersSelector.finishSetup).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.swPasswordSuccessToast);
cy.url().should("include",path.loginPath);
});
it("should verify the new user account", ()=>{
cy.login(email,usersText.password);
cy.get(usersSelector.dropdownText).should('be.visible').and('have.text', "My workspace");
common.logout();
cy.appUILogin();
common.navigateToManageUsers();
cy.contains('td', email).parent().within(() => {
cy.get('td small').should("have.text", usersText.activeStatus);
});
});
it("Should verify the archive functionality",()=>{
cy.contains('td', email).parent().within(() => {
cy.get('td button').click();
});
cy.verifyToastMessage(commonSelectors.toastMessage,usersText.archivedToast);
cy.contains('td', email).parent().within(() => {
cy.get(usersSelector.userStatus, { timeout: 9000 }).should("have.text", usersText.archivedStatus);
});
common.logout();
cy.clearAndType(commonSelectors.emailField, email);
cy.clearAndType(commonSelectors.passwordField, usersText.password);
cy.get(commonSelectors.signInButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, commonText.loginErrorToast);
cy.appUILogin();
common.navigateToManageUsers();
cy.contains('td', email).parent().within(() => {
cy.get('td button').click();
});
cy.wait(2000);
cy.window().then(win => {
cy.stub(win, 'prompt').returns(win.prompt).as('copyToClipboardPrompt');
});
cy.contains('td', email).parent().within(() => {
cy.get('td img').click();
});
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteCopiedToast);
cy.contains('td', email).parent().within(() => {
cy.get(usersSelector.userStatus, { timeout: 9000 }).should("have.text", usersText.invitedStatus);
});
cy.get('@copyToClipboardPrompt').then(prompt => {
common.logout();
cy.visit(prompt.args[0][1]);
cy.url().should("include",path.confirmInvite);
});
cy.get(usersSelector.confirmInvitePage).should("be.visible");
cy.get(usersSelector.pageLogo).should("be.visible");
cy.clearAndType(usersSelector.passwordInput, usersText.password);
cy.clearAndType(usersSelector.confirmPasswordInput, usersText.password);
cy.get(usersSelector.finishSetup).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.swPasswordSuccessToast);
cy.url().should("include",path.loginPath);
cy.appUILogin();
common.navigateToManageUsers();
cy.contains('td', email).parent().within(() => {
cy.get('td small').should("have.text", usersText.activeStatus);
});
});
});

View file

@ -2,98 +2,99 @@ import { buttonSelector } from "Selectors/button";
import {commonSelectors} from "Selectors/common";
import { buttonText } from "Texts/button";
import * as button from "Support/utils/button";
import { fake } from "Fixtures/fake";
describe("Editor- Test Button widget",()=>{
beforeEach(()=>{
cy.appLogin();
cy.createAppIfEmptyDashboard();
button.navigateToEditor();
cy.dragAndDropWidget(buttonText.widgetName);
});
const appName = (`${fake.companyName} App`);
beforeEach(()=>{
cy.appUILogin();
cy.createApp(appName);
button.navigateToEditor(appName);
cy.dragAndDropWidget(buttonText.widgetName);
});
it("should verify the properties of the button widget",()=>{
button.propertiesElements();
it("should verify the properties of the button widget",()=>{
button.propertiesElements();
cy.get(buttonSelector.buttonInputField).first().click().type(`{selectall}${buttonText.buttonText}`);
cy.get(buttonSelector.buttonProperties).click();
cy.get(buttonSelector.buttonWidget).should("have.text",buttonText.buttonText);
cy.get(buttonSelector.buttonName).should("be.visible");
cy.get(buttonSelector.buttonProperties).click();
cy.get(buttonSelector.buttonName).clear().type(` ${buttonText.invalidButtonName}{enter}`);
cy.verifyToastMessage(commonSelectors.toastMessage,buttonText.buttonNameErrToast);
cy.get(buttonSelector.buttonName).clear().type(`{selectall}${buttonText.validButtonName}{enter}`);
cy.get(buttonSelector.buttonInputField).first().click().type(`{selectall}${buttonText.buttonText}`);
cy.get(buttonSelector.buttonProperties).click();
cy.get(buttonSelector.buttonWidget).should("have.text",buttonText.buttonText);
cy.get(buttonSelector.buttonName).should("be.visible");
cy.get(buttonSelector.buttonProperties).click();
cy.get(buttonSelector.buttonName).clear().type(` ${buttonText.invalidButtonName}{enter}`);
cy.verifyToastMessage(commonSelectors.toastMessage,buttonText.buttonNameErrToast);
cy.get(buttonSelector.buttonName).clear().type(`{selectall}${buttonText.validButtonName}{enter}`);
cy.get(buttonSelector.loadingStateFx).click();
cy.get(buttonSelector.fxLoadingState).should("have.text", buttonText.falseText);
cy.get(buttonSelector.loadingStateInputFx).click();
cy.get(buttonSelector.loadingStateToggle).check();
cy.get(buttonSelector.loadingStateFx).click();
cy.get(buttonSelector.fxLoadingState).should("have.text", buttonText.trueText);
cy.get(buttonSelector.loadingStateInputFx).click();
cy.get(buttonSelector.loadingStateToggle).uncheck();
cy.get(buttonSelector.propertiesElements.propertiesAccordion).click();
cy.get(buttonSelector.loadingStateFx).click();
cy.get(buttonSelector.fxLoadingState).should("have.text", buttonText.falseText);
cy.get(buttonSelector.loadingStateInputFx).click();
cy.get(buttonSelector.loadingStateToggle).check();
cy.get(buttonSelector.loadingStateFx).click();
cy.get(buttonSelector.fxLoadingState).should("have.text", buttonText.trueText);
cy.get(buttonSelector.loadingStateInputFx).click();
cy.get(buttonSelector.loadingStateToggle).uncheck();
cy.get(buttonSelector.propertiesElements.propertiesAccordion).click();
cy.get(buttonSelector.propertiesElements.addEventListner).click();
cy.get(buttonSelector.eventHandler).click();
cy.get(buttonSelector.popoverCard).should("be.visible");
button.eventListnerCard();
cy.get(buttonSelector.messageText).click().type(buttonText.newMessage);
cy.get(buttonSelector.buttonWidget).click({force:true});
cy.get(buttonSelector.propertiesElements.addEventListner).click();
cy.get(buttonSelector.eventHandler).click();
cy.get(buttonSelector.popoverCard).should("be.visible");
button.eventListnerCard();
cy.get(buttonSelector.messageText).click().type(buttonText.newMessage);
cy.get(buttonSelector.buttonWidget).click({force:true});
cy.get(buttonSelector.desktopFx).click();
cy.get(buttonSelector.fxDesktop).should("have.text", buttonText.trueText);
cy.get(buttonSelector.desktopInputFx).click();
cy.get(buttonSelector.desktopToggle).uncheck();
cy.get(buttonSelector.desktopFx).click();
cy.get(buttonSelector.fxDesktop).should("have.text", buttonText.falseText);
cy.get(buttonSelector.desktopInputFx).click();
cy.get(buttonSelector.desktopToggle).check();
cy.get(buttonSelector.desktopFx).click();
cy.get(buttonSelector.fxDesktop).should("have.text", buttonText.trueText);
cy.get(buttonSelector.desktopInputFx).click();
cy.get(buttonSelector.desktopToggle).uncheck();
cy.get(buttonSelector.desktopFx).click();
cy.get(buttonSelector.fxDesktop).should("have.text", buttonText.falseText);
cy.get(buttonSelector.desktopInputFx).click();
cy.get(buttonSelector.desktopToggle).check();
cy.get(buttonSelector.mobileFx).click();
cy.get(buttonSelector.fxMobile).should("have.text", buttonText.falseText);
cy.get(buttonSelector.mobileInputFx).click();
cy.get(buttonSelector.mobileToggle).check();
cy.get(buttonSelector.mobileFx).click();
cy.get(buttonSelector.fxMobile).should("have.text", buttonText.trueText);
cy.get(buttonSelector.mobileInputFx).click();
cy.get(buttonSelector.mobileToggle).uncheck();
cy.get(buttonSelector.mobileFx).click();
cy.get(buttonSelector.fxMobile).should("have.text", buttonText.falseText);
cy.get(buttonSelector.mobileInputFx).click();
cy.get(buttonSelector.mobileToggle).check();
cy.get(buttonSelector.mobileFx).click();
cy.get(buttonSelector.fxMobile).should("have.text", buttonText.trueText);
cy.get(buttonSelector.mobileInputFx).click();
cy.get(buttonSelector.mobileToggle).uncheck();
button.deleteApp();
button.deleteApp();
});
});
it("should verify the styles of the button widget",()=>{
button.stylesElements();
button.colorPickerCard();
cy.get(buttonSelector.stylesFx.visibility).click();
cy.get(buttonSelector.fxVisibility).should("have.text",buttonText.trueText);
cy.get(buttonSelector.visibilityCloseFx).click();
cy.get(buttonSelector.visibilityToggle).uncheck();
cy.get(buttonSelector.stylesFx.visibility).click();
cy.get(buttonSelector.fxVisibility).should("have.text",buttonText.falseText);
cy.get(buttonSelector.visibilityCloseFx).click();
cy.get(buttonSelector.buttonWidget).should("not.be.visible");
cy.get(buttonSelector.visibilityToggle).check();
it("should verify the styles of the button widget",()=>{
button.stylesElements();
button.colorPickerCard();
cy.get(buttonSelector.stylesFx.visibility).click();
cy.get(buttonSelector.fxVisibility).should("have.text",buttonText.trueText);
cy.get(buttonSelector.visibilityCloseFx).click();
cy.get(buttonSelector.visibilityToggle).uncheck();
cy.get(buttonSelector.stylesFx.visibility).click();
cy.get(buttonSelector.fxVisibility).should("have.text",buttonText.falseText);
cy.get(buttonSelector.visibilityCloseFx).click();
cy.get(buttonSelector.buttonWidget).should("not.be.visible");
cy.get(buttonSelector.visibilityToggle).check();
cy.get(buttonSelector.stylesFx.disable).click();
cy.get(buttonSelector.fxDisable).should("have.text",buttonText.falseText);
cy.get(buttonSelector.disableCloseFx).click();
cy.get(buttonSelector.disableToggle).check();
cy.get(buttonSelector.stylesFx.disable).click();
cy.get(buttonSelector.fxDisable).should("have.text",buttonText.trueText);
cy.get(buttonSelector.disableCloseFx).click();
cy.get(buttonSelector.buttonWidget).should("be.disabled");
cy.get(buttonSelector.disableToggle).uncheck();
cy.get(buttonSelector.buttonWidget).should("be.enabled");
cy.get(buttonSelector.stylesFx.disable).click();
cy.get(buttonSelector.fxDisable).should("have.text",buttonText.falseText);
cy.get(buttonSelector.disableCloseFx).click();
cy.get(buttonSelector.disableToggle).check();
cy.get(buttonSelector.stylesFx.disable).click();
cy.get(buttonSelector.fxDisable).should("have.text",buttonText.trueText);
cy.get(buttonSelector.disableCloseFx).click();
cy.get(buttonSelector.buttonWidget).should("be.disabled");
cy.get(buttonSelector.disableToggle).uncheck();
cy.get(buttonSelector.buttonWidget).should("be.enabled");
cy.get(buttonSelector.stylesInput.borderRadiusInputField).clear().type("15");
cy.get(buttonSelector.stylesFx.borderRadius).click();
cy.get(buttonSelector.fxBorderRadius).should("have.text", buttonText.borderRadiusInput)
cy.get(buttonSelector.stylesInput.borderRadiusInputField).clear().type("15");
cy.get(buttonSelector.stylesFx.borderRadius).click();
cy.get(buttonSelector.fxBorderRadius).should("have.text", buttonText.borderRadiusInput)
button.deleteApp();
});
button.deleteApp();
});
});

View file

@ -34,21 +34,37 @@ Cypress.Commands.add("appLogin",()=>{
cy.visit('/');
})
Cypress.Commands.add('createAppIfEmptyDashboard', fn => {
cy.get('.empty-title').then(($title => {
Cypress.Commands.add('createApp',(appName) => {
cy.get('body').then(($title => {
if ($title.text().includes('You can get started by creating a new application or by creating an application using a template in ToolJet Library.')) {
cy.get(".empty-action > :nth-child(1)").click();
cy.get(".modal-footer > .btn").click()
cy.get(commonSelectors.emptyAppCreateButton).click();
cy.get(commonSelectors.createButton).click();
cy.wait(1000);
cy.get('body').then($el =>{
if($el.text().includes('Skip')){
cy.get(commonSelectors.skipButton).click();
}
else{
cy.log("instructions modal is skipped ")
cy.log("instructions modal is skipped ");
}
});
cy.go('back')
cy.clearAndType(commonSelectors.appNameInput, appName);
cy.get(commonSelectors.backButton).click();
}
else{
cy.get(commonSelectors.appCreateButton).click();
cy.get(commonSelectors.createButton).click();
cy.wait(1000);
cy.get('body').then($el =>{
if($el.text().includes('Skip')){
cy.get(commonSelectors.skipButton).click();
}
else{
cy.log("instructions modal is skipped ");
}
});
cy.clearAndType(commonSelectors.appNameInput, appName);
cy.get(commonSelectors.backButton).click();
}
}))
});

View file

@ -1,97 +1,103 @@
import { buttonSelector } from "Selectors/button";
import { buttonText } from "Texts/button";
import {commonSelectors} from "Selectors/common";
import { commonText } from "Texts/common";
export const propertiesElements = () => {
cy.get(buttonSelector.buttonWidget).dblclick();
cy.get(buttonSelector.buttonWidget).dblclick();
cy.get(buttonSelector.buttonProperties).should("be.visible").and("have.text",buttonText.buttonProperties);
cy.get(buttonSelector.buttonName).should("be.visible");
cy.get(buttonSelector.buttonProperties).should("be.visible").and("have.text",buttonText.buttonProperties);
cy.get(buttonSelector.buttonName).should("be.visible");
cy.get(buttonSelector.propertiesElements.eventsAccordion).click();
cy.get(buttonSelector.propertiesElements.layoutAccordion).click();
for(const elements in buttonSelector.propertiesElements){
cy.get(buttonSelector.propertiesElements[elements]).should("be.visible").and("have.text",buttonText.propertiesElements[elements]);
}
cy.get(buttonSelector.propertiesElements.eventsAccordion).click();
cy.get(buttonSelector.propertiesElements.layoutAccordion).click();
for(const elements in buttonSelector.propertiesElements){
cy.get(buttonSelector.propertiesElements[elements]).should("be.visible").and("have.text",buttonText.propertiesElements[elements]);
}
cy.get(buttonSelector.loadingStateToggle).should("be.visible");
cy.get(buttonSelector.loadingStateFx).should("be.visible").and("have.text",buttonText.fxButton);
cy.get(buttonSelector.desktopToggle).should("be.visible");
cy.get(buttonSelector.desktopFx).should("be.visible").and("have.text",buttonText.fxButton);
cy.get(buttonSelector.mobileToggle).should("be.visible");
cy.get(buttonSelector.mobileFx).should("be.visible").and("have.text",buttonText.fxButton);
cy.get(buttonSelector.loadingStateToggle).should("be.visible");
cy.get(buttonSelector.loadingStateFx).should("be.visible").and("have.text",buttonText.fxButton);
cy.get(buttonSelector.desktopToggle).should("be.visible");
cy.get(buttonSelector.desktopFx).should("be.visible").and("have.text",buttonText.fxButton);
cy.get(buttonSelector.mobileToggle).should("be.visible");
cy.get(buttonSelector.mobileFx).should("be.visible").and("have.text",buttonText.fxButton);
};
export const stylesElements = () => {
cy.get(buttonSelector.buttonWidget).dblclick();
cy.get(buttonSelector.buttonStyles).should("be.visible").and("have.text",buttonText.buttonStyles);
cy.get(buttonSelector.buttonStyles).click();
for(const elements in buttonSelector.stylesElements){
cy.get(buttonSelector.stylesElements[elements]).should("be.visible").and("have.text",buttonText.stylesElements[elements]);
}
for(const inputs in buttonSelector.stylesInput){
cy.get(buttonSelector.stylesInput[inputs]).should("be.visible");
}
for(const fx in buttonSelector.stylesFx){
cy.get(buttonSelector.stylesFx[fx]).should("be.visible").and("have.text",buttonText.fxButton);
}
cy.get(buttonSelector.visibilityToggle).should("be.visible");
cy.get(buttonSelector.disableToggle).should("be.visible");
cy.get(buttonSelector.buttonWidget).dblclick();
cy.get(buttonSelector.buttonStyles).should("be.visible").and("have.text",buttonText.buttonStyles);
cy.get(buttonSelector.buttonStyles).click();
for(const elements in buttonSelector.stylesElements){
cy.get(buttonSelector.stylesElements[elements]).should("be.visible").and("have.text",buttonText.stylesElements[elements]);
}
for(const inputs in buttonSelector.stylesInput){
cy.get(buttonSelector.stylesInput[inputs]).should("be.visible");
}
for(const fx in buttonSelector.stylesFx){
cy.get(buttonSelector.stylesFx[fx]).should("be.visible").and("have.text",buttonText.fxButton);
}
cy.get(buttonSelector.visibilityToggle).should("be.visible");
cy.get(buttonSelector.disableToggle).should("be.visible");
};
export const deleteApp = () => {
cy.go('back');
cy.get(commonSelectors.appCardOptions).click();
cy.get(commonSelectors.deleteApp).click();
cy.get(commonSelectors.confirmButton).click();
cy.get(commonSelectors.autoSave, { timeout: 9000 }).should("have.text", commonText.autoSave);
cy.get(commonSelectors.backButton).click();
cy.get(commonSelectors.appCardOptions).first().click();
cy.get(commonSelectors.deleteApp).click();
cy.get(commonSelectors.confirmButton).click();
};
export const navigateToEditor = () => {
cy.get(commonSelectors.appCard).click();
cy.get(commonSelectors.editButton).click();
export const navigateToEditor = (appName) => {
cy.contains('div', appName).parent().within(() => {
cy.get('div, button').click();
});
cy.get(commonSelectors.editButton).first().click();
};
export const eventListnerCard = () => {
cy.get(buttonSelector.eventLabel).should("be.visible").and("have.text", buttonText.eventLabel);
cy.get(buttonSelector.eventSelection).should("be.visible");
cy.get(buttonSelector.actionLabel).should("be.visible").and("have.text", buttonText.actionLabel);
cy.get(buttonSelector.actionSelection).should("be.visible");
cy.get(buttonSelector.actionOption).should("be.visible").and("have.text", buttonText.actionOption);
cy.get(buttonSelector.messageLabel).should("be.visible").and("have.text", buttonText.messageLabel);
cy.get(buttonSelector.messageText).should("be.visible");
cy.get(buttonSelector.alertTypeLabel).should("be.visible").and("have.text", buttonText.alertTypeLabel);
cy.get(buttonSelector.alertMessageType).should("be.visible");
cy.get(buttonSelector.eventLabel).should("be.visible").and("have.text", buttonText.eventLabel);
cy.get(buttonSelector.eventSelection).should("be.visible");
cy.get(buttonSelector.actionLabel).should("be.visible").and("have.text", buttonText.actionLabel);
cy.get(buttonSelector.actionSelection).should("be.visible");
cy.get(buttonSelector.actionOption).should("be.visible").and("have.text", buttonText.actionOption);
cy.get(buttonSelector.messageLabel).should("be.visible").and("have.text", buttonText.messageLabel);
cy.get(buttonSelector.messageText).should("be.visible");
cy.get(buttonSelector.alertTypeLabel).should("be.visible").and("have.text", buttonText.alertTypeLabel);
cy.get(buttonSelector.alertMessageType).should("be.visible");
};
export const colorPickerCard = () => {
cy.get(buttonSelector.backgroundColorSelector).should("be.visible").click();
cy.get(buttonSelector.colorPickCard).should("be.visible");
for(const elements in buttonSelector.backgroundColor){
cy.get(buttonSelector.backgroundColor[elements]).should("be.visible");
}
cy.get(buttonSelector.stylesFx.backgroundColor).click();
cy.get(buttonSelector.backgroundColorInput).should("have.text", buttonText.backgroundColorInput);
cy.get(buttonSelector.backgroundColorCloseFx).click();
cy.get(buttonSelector.backgroundColorSelector).should("be.visible").click();
cy.get(buttonSelector.colorPickCard).should("be.visible");
for(const elements in buttonSelector.backgroundColor){
cy.get(buttonSelector.backgroundColor[elements]).should("be.visible");
}
cy.get(buttonSelector.stylesFx.backgroundColor).click();
cy.get(buttonSelector.backgroundColorInput).should("have.text", buttonText.backgroundColorInput);
cy.get(buttonSelector.backgroundColorCloseFx).click();
cy.get(buttonSelector.textColorSelector).should("be.visible").click();
cy.get(buttonSelector.colorPickCard).should("be.visible");
for(const elements in buttonSelector.textColor){
cy.get(buttonSelector.textColor[elements]).should("be.visible");
}
cy.get(buttonSelector.stylesFx.textColor).click();
cy.get(buttonSelector.textColorInput).should("have.text", buttonText.textColorInput);
cy.get(buttonSelector.textColorCloseFx).click();
cy.get(buttonSelector.textColorSelector).should("be.visible").click();
cy.get(buttonSelector.colorPickCard).should("be.visible");
for(const elements in buttonSelector.textColor){
cy.get(buttonSelector.textColor[elements]).should("be.visible");
}
cy.get(buttonSelector.stylesFx.textColor).click();
cy.get(buttonSelector.textColorInput).should("have.text", buttonText.textColorInput);
cy.get(buttonSelector.textColorCloseFx).click();
cy.get(buttonSelector.loaderColorSelector).should("be.visible").click();
cy.get(buttonSelector.colorPickCard).should("be.visible");
for(const elements in buttonSelector.loaderColor){
cy.get(buttonSelector.loaderColor[elements]).should("be.visible");
}
cy.get(buttonSelector.stylesFx.loaderColor).click();
cy.get(buttonSelector.loaderColorInput).should("have.text", buttonText.loaderColorInput);
cy.get(buttonSelector.loaderColorCloseFx).click();
cy.get(buttonSelector.loaderColorSelector).should("be.visible").click();
cy.get(buttonSelector.colorPickCard).should("be.visible");
for(const elements in buttonSelector.loaderColor){
cy.get(buttonSelector.loaderColor[elements]).should("be.visible");
}
cy.get(buttonSelector.stylesFx.loaderColor).click();
cy.get(buttonSelector.loaderColorInput).should("have.text", buttonText.loaderColorInput);
cy.get(buttonSelector.loaderColorCloseFx).click();
};

View file

@ -1,6 +1,7 @@
import { path } from "Texts/common";
import { usersSelector } from "Selectors/manageUsers";
import { profileSelector } from "Selectors/profile";
import { commonSelectors } from "Selectors/common";
export const navigateToProfile=()=>{
cy.get(profileSelector.profileDropdown).invoke("show");
@ -18,4 +19,16 @@ export const navigateToManageUsers=()=>{
cy.get(usersSelector.dropdown).invoke("show");
cy.contains("Manage Users").click();
cy.url().should("include",path.manageUsers );
};
export const navigateToManageGroups=()=>{
cy.get(commonSelectors.dropdown).invoke("show");
cy.contains("Manage Groups").click();
cy.url().should("include",path.manageGroups );
}
export const navigateToManageSSO=()=>{
cy.get(commonSelectors.dropdown).invoke("show");
cy.contains("Manage SSO").click();
cy.url().should("include",path.manageSSO );
};

View file

@ -0,0 +1,75 @@
import { groupsSelector } from "Selectors/manageGroups";
import { groupsText } from "Texts/manageGroups";
import {commonSelectors} from "Selectors/common";
export const manageGroupsElements = () =>{
cy.get(groupsSelector.pageTitle).should("be.visible").and("have.text", groupsText.pageTitle);
cy.get(groupsSelector.createNewGroupButton).should("be.visible").and("have.text", groupsText.createNewGroupButton);
cy.get(groupsSelector.tableHeader).should("be.visible").and("have.text", groupsText.tableHeader);
cy.get(groupsSelector.groupName).contains(groupsText.allUsers).should("be.visible").and("have.text", groupsText.allUsers);
cy.get(groupsSelector.groupName).contains(groupsText.admin).should("be.visible").and("have.text", groupsText.admin);
cy.get(groupsSelector.createNewGroupButton).should("be.visible").click();
cy.get(groupsSelector.cardTitle).should("be.visible").and("have.text", groupsText.cardTitle);
cy.get(groupsSelector.groupNameInput).should("be.visible");
cy.get(groupsSelector.cancelButton).should("be.visible").and("have.text", groupsText.cancelButton);
cy.get(groupsSelector.createGroupButton).should("be.visible").and("have.text", groupsText.createGroupButton);
cy.get(groupsSelector.cancelButton).click();
cy.get(groupsSelector.groupName).contains(groupsText.allUsers).click();
cy.get(groupsSelector.userGroup).should("be.visible").and("have.text", groupsText.userGroup);
cy.get(groupsSelector.groupName).should("be.visible").and("have.text", groupsText.allUsers);
cy.get(groupsSelector.searchBox).should("be.visible");
cy.get(groupsSelector.addButton).should("be.visible").and("have.text", groupsText.addButton);
cy.get(groupsSelector.nameTableHeader).should("be.visible").and("have.text", groupsText.nameTableHeader);
cy.get(groupsSelector.permissionstableHedaer).should("be.visible").and("have.text", groupsText.permissionstableHedaer);
cy.get(groupsSelector.usersLink).click();
cy.get(groupsSelector.searchBox).should("be.visible");
cy.get(groupsSelector.addButton).should("be.visible").and("have.text", groupsText.addButton);
cy.get(groupsSelector.nameTableHeader).should("be.visible").and("have.text", groupsText.nameTableHeader);
cy.get(groupsSelector.emailTableHeader).should("be.visible").and("have.text", groupsText.emailTableHeader);
cy.get(groupsSelector.permissionsLink).click();
cy.get(groupsSelector.resourcesApps).should("be.visible").and("have.text", groupsText.resourcesApps);
cy.get(groupsSelector.permissionstableHedaer).should("be.visible").and("have.text", groupsText.permissionstableHedaer);
cy.get(groupsSelector.resourcesApps).should("be.visible").and("have.text", groupsText.resourcesApps);
cy.get(groupsSelector.appsCreateCheck).should("be.visible").check();
cy.verifyToastMessage(commonSelectors.toastMessage, groupsText.permissionUpdatedToast);
cy.get(groupsSelector.appsCreateLabel).should("be.visible").and("have.text", groupsText.createLabel);
cy.get(groupsSelector.appsCreateCheck).uncheck();
cy.get(groupsSelector.appsDeleteCheck).should("be.visible").check();
cy.get(groupsSelector.appsDeleteLabel).should("be.visible").and("have.text", groupsText.deleteLabel);
cy.get(groupsSelector.appsDeleteCheck).uncheck();
cy.get(groupsSelector.resourcesFolders).should("be.visible").and("have.text", groupsText.resourcesFolders);
cy.get(groupsSelector.foldersCreateCheck).should("be.visible").check();
cy.get(groupsSelector.foldersCreateLabel).should("be.visible").and("have.text", groupsText.folderCreateLabel);
cy.get(groupsSelector.foldersCreateCheck).uncheck();
cy.get(groupsSelector.userGroup).click();
cy.get(groupsSelector.groupName).contains(groupsText.admin).click();
cy.get(groupsSelector.userGroup).should("be.visible").and("have.text", groupsText.userGroup);
cy.get(groupsSelector.groupName).should("be.visible").and("have.text", groupsText.admin);
cy.get(groupsSelector.searchBox).should("be.visible");
cy.get(groupsSelector.addButton).should("be.visible").and("have.text", groupsText.addButton);
cy.get(groupsSelector.nameTableHeader).should("be.visible").and("have.text", groupsText.nameTableHeader);
cy.get(groupsSelector.permissionstableHedaer).should("be.visible").and("have.text", groupsText.permissionstableHedaer);
cy.get(groupsSelector.usersLink).click();
cy.get(groupsSelector.searchBox).should("be.visible");
cy.get(groupsSelector.addButton).should("be.visible").and("have.text", groupsText.addButton);
cy.get(groupsSelector.nameTableHeader).should("be.visible").and("have.text", groupsText.nameTableHeader);
cy.get(groupsSelector.emailTableHeader).should("be.visible").and("have.text", groupsText.emailTableHeader);
cy.get(groupsSelector.permissionsLink).click();
cy.get(groupsSelector.resourcesApps).should("be.visible").and("have.text", groupsText.resourcesApps);
cy.get(groupsSelector.permissionstableHedaer).should("be.visible").and("have.text", groupsText.permissionstableHedaer);
cy.get(groupsSelector.resourcesApps).should("be.visible").and("have.text", groupsText.resourcesApps);
cy.get(groupsSelector.appsCreateCheck).should("be.visible").and("be.disabled");
cy.get(groupsSelector.appsDeleteCheck).should("be.visible").and("be.disabled");
cy.get(groupsSelector.foldersCreateLabel).should("be.visible").and("have.text", groupsText.folderCreateLabel);
cy.get(groupsSelector.foldersCreateCheck).should("be.visible").and("be.disabled");
cy.get(groupsSelector.userGroup).click();
};

View file

@ -0,0 +1,132 @@
import { commonSelectors } from "Selectors/common";
import { ssoSelector } from "Selectors/manageSSO";
import * as common from "Support/utils/common";
import { ssoText } from "Texts/manageSSO";
export const generalSettings=()=>{
cy.get(ssoSelector.enableCheckbox).then(($el) => {
if($el.is(':checked')){
cy.get(ssoSelector.enableCheckbox).check()
cy.get(ssoSelector.cancelButton).click();
cy.get(ssoSelector.enableCheckbox).should("be.checked");
cy.get(ssoSelector.enableCheckbox).check();
cy.clearAndType(ssoSelector.domainInput, ssoText.allowedDomain);
cy.get(ssoSelector.saveButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.ssoToast);
}
else{
cy.get(ssoSelector.enableCheckbox).check()
cy.get(ssoSelector.cancelButton).click();
cy.get(ssoSelector.enableCheckbox).should("not.be.checked");
cy.get(ssoSelector.enableCheckbox).check();
cy.clearAndType(ssoSelector.domainInput, ssoText.allowedDomain);
cy.get(ssoSelector.saveButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.ssoToast);
}
});
};
export const googleSSO=()=>{
cy.get(ssoSelector.enableCheckbox).then(($el) => {
if($el.is(':checked')){
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.get(ssoSelector.enableCheckbox).uncheck();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.googleDisableToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.get(ssoSelector.enableCheckbox).check();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.googleEnabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.clearAndType(ssoSelector.clientIdInput, ssoText.clientId);
cy.get(ssoSelector.saveButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.ssoToast2);
cy.clearAndType(ssoSelector.clientIdInput, ssoText.testClientId);
cy.get(ssoSelector.cancelButton).click();
cy.get(ssoSelector.clientIdInput).should("have.value", ssoText.clientId);
}
else{
cy.get(ssoSelector.enableCheckbox).check();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.googleEnabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.get(ssoSelector.enableCheckbox).uncheck();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.googleDisableToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.get(ssoSelector.enableCheckbox).check();
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.clearAndType(ssoSelector.clientIdInput, ssoText.clientId);
cy.get(ssoSelector.saveButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.ssoToast2);
cy.clearAndType(ssoSelector.clientIdInput, ssoText.testClientId);
cy.get(ssoSelector.cancelButton).click();
cy.get(ssoSelector.clientIdInput).should("have.value", ssoText.clientId);
}
});
};
export const gitSSO=()=>{
cy.get(ssoSelector.enableCheckbox).then(($el) => {
if($el.is(':checked')){
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.get(ssoSelector.enableCheckbox).uncheck();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.gitDisabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.wait(1000);
cy.get(ssoSelector.enableCheckbox).check();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.gitEnabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.clearAndType(ssoSelector.clientIdInput, ssoText.clientId);
cy.clearAndType(ssoSelector.clientSecretInput, ssoText.testClientId);
cy.get(ssoSelector.saveButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.ssoToast2);
}
else{
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.get(ssoSelector.enableCheckbox).check();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.gitEnabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.wait(1000);
cy.get(ssoSelector.enableCheckbox).uncheck();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.gitDisabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.wait(1000);
cy.get(ssoSelector.enableCheckbox).check();
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.clearAndType(ssoSelector.clientIdInput, ssoText.clientId);
cy.clearAndType(ssoSelector.clientSecretInput, ssoText.testClientId);
cy.get(ssoSelector.saveButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.ssoToast2);
}
});
}
export const password=()=>{
cy.get(ssoSelector.enableCheckbox).then(($el) => {
if($el.is(':checked')){
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.get(ssoSelector.enableCheckbox).uncheck();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.passwordDisabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.wait(1000);
cy.get(ssoSelector.enableCheckbox).check();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.passwordEnabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
}
else{
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.get(ssoSelector.enableCheckbox).check();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.passwordEnabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.enabledLabel);
cy.wait(1000);
cy.get(ssoSelector.enableCheckbox).uncheck();
cy.verifyToastMessage(commonSelectors.toastMessage, ssoText.passwordDisabledToast);
cy.get(ssoSelector.statusLabel).should("be.visible").and("have.text",ssoText.disabledLabel);
cy.wait(1000);
cy.get(ssoSelector.enableCheckbox).check();
}
});
};

View file

@ -66,6 +66,22 @@ Query results can be transformed using transformations. Read our transformations
- [Query](#query)
- [Insert Record ](#insert-record)
- [Delete Record ](#delete-record)
- [Update Record](#update-record)
- [Create View](#create-view)
- [Create Table](#create-table)
- [Delete Table](#create-table)
### List Datasets
@ -103,8 +119,64 @@ Return list of tables within a dataset
Return data based on the `Query`. `Query options` ([Reference](https://cloud.google.com/bigquery/docs/reference/rest/v2/Job)), and `Query result options` ([Reference](https://cloud.google.com/nodejs/docs/reference/bigquery/latest/overview#_google_cloud_bigquery_QueryResultsOptions_type)).
<div style={{textAlign: 'center'}}>
![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/query.png)
</div>
</div>
### Insert Record
- To insert a record.
<div style={{textAlign: 'center'}}>
![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-insert.png)
</div>
### Delete Record
- To delete a record.
<div style={{textAlign: 'center'}}>
![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-delete.png)
</div>
:::info
NOTE: Be careful when deleting records in a table. If you omit the WHERE clause, all records in the table will be deleted!
:::
### Update Record
- To update a record.
<div style={{textAlign: 'center'}}>
![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-update.png)
</div>
:::info
NOTE: Be careful when deleting records in a table. If you omit the WHERE clause, all records in the table will be updated!
:::
### Create View
- To create a view.
<div style={{textAlign: 'center'}}>
![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-view.png)
</div>
### Create Table
- To create a table.
:::info
NOTE: visit -https://github.com/googleapis/nodejs-bigquery/blob/main/samples/createTable.js for more info on schema.
:::
### Delete Table
- To delete a table.

View file

@ -5,21 +5,21 @@ title: General Settings
# Single Sign-On General Settings
Select `Manage SSO` from workspace options
- Select `Manage SSO` from workspace options
<div style={{textAlign: 'center'}}>
<div style={{textAlign: 'center'}}>
![ToolJet - SSO configs](/img/password-login/organization-menu.png)
![ToolJet - SSO configs](/img/password-login/organization-menu.png)
</div>
</div>
Select `General Settings`
- Select `General Settings`
<div style={{textAlign: 'center'}}>
<div style={{textAlign: 'center'}}>
![ToolJet - SSO configs](/img/sso/general/general-settings.png)
![ToolJet - SSO configs](/img/sso/general/general-settings.png)
</div>
</div>
## Enable Signup
@ -28,3 +28,7 @@ You can enable/disable `Enable signup`. If it is enabled, new account will be cr
## Allowed domains
You can set allowed domains for SSO login, can add multiple domains comma separated. Allowed all domains by default.
## Login URL
You can use the login URL to login directly to the workspace. This will be hidden if Multi-Workspace is disabled.

View file

@ -0,0 +1,67 @@
---
id: azuread
title: AzureAD
---
# AzureAD Single Sign-on
:::info
To construct a Well Known URL refer this link :: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc
:::
- Open your organisation page and select `app registration`
<div style={{textAlign: 'center'}}>
![ToolJet - AzureAD app registration](/img/sso/azuread/azure-app-reg.png)
</div>
- Select `new registration`
<div style={{textAlign: 'center'}}>
![ToolJet - AzureAD app registration](/img/sso/azuread/select-new-reg-azure.png)
</div>
- Open your organisation page and select app registration.
- Enter name, select supported account type and enter the redirect URL which can be copied from `Manage SSO -> Open Id -> Redirect URL, click on register`.
<div style={{textAlign: 'center'}}>
![ToolJet - AzureAD app registration](/img/sso/azuread/azure-3.png)
</div>
- Application will be registered and will be able to view the details
- Configure Application (Client) ID as `client id` in Open Id configuration page.
<div style={{textAlign: 'center'}}>
![ToolJet - AzureAD app registration](/img/sso/azuread/azure-4-cred.png)
</div>
- Click on `Add certificate or secret` next to the **Client credentials**.
- Click on `+New Client Secret`
<div style={{textAlign: 'center'}}>
![ToolJet - AzureAD app registration](/img/sso/azuread/azure8.png)
</div>
- Give a description, set the expiry, and then click on the `Add` button.
<div style={{textAlign: 'center'}}>
![ToolJet - AzureAD app registration](/img/sso/azuread/azure7.png)
</div>
- Secret will be created, copy value and add it to the `client secret ` section of Open Id SSO config.
- You can brand the redirect page using the branding and properties option.
<div style={{textAlign: 'center'}}>
![ToolJet - AzureAD app registration](/img/sso/azuread/azure9.png)
</div>

View file

@ -0,0 +1,54 @@
---
id: okta
title: Okta
---
# Okta Single Sign-on
- Sign in to [Okta developer console](https://developer.okta.com/)
- Go to the `Applications` section and click on the `Create App Integration`
<div style={{textAlign: 'center'}}>
![ToolJet - Okta create application](/img/sso/okta/create-app.png)
</div>
- Select `Sign-in method` as `OIDC - OpenID Connect` and `Application type` as `Web Application`. Go to the next step
<div style={{textAlign: 'center'}}>
![ToolJet - Okta create application](/img/sso/okta/create-app-s1.png)
</div>
- Enter `App integration name` and then enter `Sign-in redirect URIs` as `<YOUR-DOMAIN>/sso/okta`.
<div style={{textAlign: 'center'}}>
![ToolJet - Okta create application](/img/sso/okta/create-app-s2.png)
</div>
- Create application and configure `Client Credentials` in the UI.
<div style={{textAlign: 'center'}}>
![ToolJet - Okta create application](/img/sso/okta/create-app-s4.png)
</div>
- If you wish to show your application on Okta, edit the application and select `Login initiated by` section as `Either Okta or App`, set visibility according to your preference and `Login flow` should `Redirect to app to initiate login (OIDC Compliant)`.
<div style={{textAlign: 'center'}}>
![ToolJet - Okta create application](/img/sso/okta/create-app-s5.png)
</div>
:::info Change Grant type
To change the Login flow to `Redirect to app to initiate login (OIDC Compliant)`, its mandatory to change the `Grant type` - `Client acting on behalf of a user` section to `Implicit (hybrid)` and tick `Allow Access Token with implicit grant type`.
:::
- The Okta sign-in button will now be available in your ToolJet login screen.
:::info
To find Well Known URL refer this Link: https://developer.okta.com/docs/concepts/auth-servers/#org-authorization-server
:::

View file

@ -0,0 +1,35 @@
---
id: setup
title: Setup
---
# Configure OpenId Connect Single Sign-on
:::info
NOTE: This feature is available <b>only for Enterprise edition</b>.
:::
- Select `Manage SSO` from workspace options
<div style={{textAlign: 'center'}}>
![ToolJet - SSO configs](/img/password-login/organization-menu.png)
</div>
- Select `OpenId Connect`.
<div style={{textAlign: 'center'}}>
![ToolJet - SSO configs](/img/sso/openid/openid-select.png)
</div>
<div style={{textAlign: 'center'}}>
![ToolJet - SSO configs](/img/sso/openid/openid.png)
</div>
- Find and set **Name**, **Client Id**, **Client Secret**, and **Well Known URL** from your Open Id provider.

View file

@ -0,0 +1,158 @@
---
id: user-lifecycle
title: User Lifecycle
---
# User Lifecycle
## Single-Workspace
### User onboarding
- If no user is present in the system, there will be `Sign-up` option in the login page. User can sign up by entering their email address. Tooljet will be sending a welcome email with activation URL to the email address. User can follow the activation URL and onboard to ToolJet.
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace sign up](/img/user-lifecycle/single-ws-signup.png)
</div>
- User with admin privileges can invite members
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace invite user](/img/user-lifecycle/user-invite-sw.png)
</div>
- Invited user will receive welcome email with activation URL, unregistered user can follow the link and setup Tooljet account
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace accept invite](/img/user-lifecycle/accept-invite-sw.png)
</div>
- Invited user can onboard through SSO login, without using an invitation link
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace accept invite](/img/user-lifecycle/sso-onboard-sw.png)
</div>
- If `enable signup` option in enabled in SSO general settings, user can setup account through SSO login without an invite
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace sign up using SSO](/img/user-lifecycle/sso-enable-signup-sw.png)
</div>
### Archive user
- User can be archived by workspace admin from using `Manage User` page
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace Archive user](/img/user-lifecycle/archive-user.png)
</div>
### Unarchive user
- User can be unArchived by workspace admin from using `Manage User` page
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace Unarchive user](/img/user-lifecycle/unarchive-sw.png)
</div>
:::info
Archive or unarchive will affect user login, user won't be able to login using email id and password unless user is in active state
:::
## Multi-Workspace
:::info
Check Multi-workspace docs [here](/docs/tutorial/multiworkspace).
:::
### User onboarding
- User can sign up using the sign up link provided on the login page, user will receive a welcome email with activation link. New workspace will be created for the user.
<div style={{textAlign: 'center'}}>
![ToolJet - Multi-Workspace sign up](/img/user-lifecycle/signup-mw.png)
</div>
- Users can be added to multiple workspaces. Users can create their own workspaces and manage them.
<div style={{textAlign: 'center'}}>
![ToolJet - Multi-Workspace sign up](/img/user-lifecycle/user-mw.png)
</div>
- Existing user in active state for a workspace can be invited and on boarded to other workspaces, User will receive an invitation email with join link. If a user does not exist in the system, then they will receive a welcome email to setup the account, user can follow the link and on setup the account, once its done the user will be assigned to the new workspace created for the user.
<div style={{textAlign: 'center'}}>
![ToolJet - Multi-Workspace sign up](/img/user-lifecycle/invite-link-mw.png)
</div>
- Invited user can onboard through SSO login, without using an invitation link from the workspace [login page](/docs/user-authentication/general-settings#login-url)
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace accept invite](/img/user-lifecycle/sso-onboard-sw.png)
</div>
- If `enable sign up` option in enabled in SSO [general settings](/docs/user-authentication/general-settings#enable-signup) for the workspace, user can setup account through SSO login without an invite from the workspace [login page](/docs/user-authentication/general-settings#login-url)
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace sign up using SSO](/img/user-lifecycle/sso-enable-signup-sw.png)
</div>
### Archive user
- User can be archived by workspace admin from using `Manage User` page
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace Archive user](/img/user-lifecycle/archive-user.png)
</div>
### Unarchive user
- User can be unarchive by workspace admin from using `Manage User` page
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace Unarchive user](/img/user-lifecycle/unarchive-user-mw.png)
</div>
:::info
Archive or unarchive will not affect user login, user can login and use other workspaces where user is in active state.
:::
### Switch between workspaces
<div style={{textAlign: 'center'}}>
![ToolJet - Single-Workspace sign up using SSO](/img/user-lifecycle/switch.png)
</div>
## User status
| Status | Able to log in | How to activate |
| -------- | ---------------- | ------------------------------------ |
| active | Yes | |
| invited | No (Yes with SSO)| Login through SSO or invitation link |
| archived | No | Not able to activate. Invite from `Manage Users` page, status will be changed to invited |

100
docs/docs/widgets/kanban.md Normal file
View file

@ -0,0 +1,100 @@
---
id: kanban
title: Kanban
---
# Kanban
Kanban widget allows you to visually organize and prioritize your tasks with a transparent workflow. You can set the number of columns to display, enable/disable the add cards button, and bind data to the cards.
<div style={{textAlign: 'center'}}>
![ToolJet - Kanban widget](/img/widgets/kanban/kanban.png)
</div>
## Events
To add an event, click on the widget handle to open the widget properties on the right sidebar. Go to the **Events** section and click on **Add handler**.
- [Card added](#card-added)
- [Card removed](#card-removed)
- [Card moved](#card-moved)
- [Card selected](#card-selected)
- [Card updated](#card-updated)
Just like any other event on ToolJet, you can set multiple handlers for any of the above mentioned events.
<div style={{textAlign: 'center'}}>
![ToolJet - Kanban widget](/img/widgets/kanban/kanban-events.png)
</div>
## Properties
<div style={{textAlign: 'center'}}>
:::caution
Please keep in mind that you need to provide an `id` for each card in the `Card data` field <br />
and this `id` must be of type string.
:::
![ToolJet - Kanban widget](/img/widgets/kanban/properties.png)
</div>
| Properties | description | Expected value |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Columns | Enter the columns data - `id` and `title` in the form of array of objects or from a query that returns an array of objects. | `{{[{ "id": "1", "title": "to do" },{ "id": "2", "title": "in progress" },{ "id": "2", "title": "Completed" }]}}` or `{{queries.xyz.data}}` |
| Card data | Enter the cards data - `id`, `title` and `columnId` in the form of array of objects or from a query that returns an array of objects. | `{{[{ id: "01", title: "one", columnId: "1" },{ id: "02", title: "two", columnId: "1" },{ id: "03", title: "three", columnId: "2" }]}}` or `{{queries.abc.data}}` |
| Enable Add Card | This property allows you to show or hide the `Add Cards` button at the bottom of every column. | By deafult its enabled, you can programmatically set `{{true}}` or `{{false}}` enable/disable button by clicking on the `Fx` next to it |
## General
<b>Tooltip:</b> Set a tooltip text to specify the information about the data/kanban when the user moves the mouse pointer over the widget.
## Layout
<div style={{textAlign: 'center'}}>
![ToolJet - Kanban widget](/img/widgets/kanban/layout.png)
</div>
| Layout | description | Expected value |
| --------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| Show on desktop | Toggle on or off to display the widget in desktop view. | You can programmatically set the value by clicking on `Fx` to set the value `{{true}}` or `{{false}}` |
| Show on mobile | Toggle on or off to display the widget in mobile view. | You can programmatically set the value by clicking on `Fx` to set the value `{{true}}` or `{{false}}` |
## Styles
<div style={{textAlign: 'center'}}>
![ToolJet - List view widget](/img/widgets/kanban/styles.png)
</div>
| Style | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Disable | If disabled or set to `{{false}}` the widget will be locked and becomes non-functional. By default, its disabled i.e. its value is set to `{{true}}` . |
| Visibility | This is to control the visibility of the widget. If `{{false}}`/disabled the widget will not visible after the app is deployed. By default, it's enabled (set to `{{true}}`). |
| Width | This property sets the width of the column. |
| Accent color | You can change the accent color of the column title by entering the Hex color code or choosing a color of your choice from the color picker. |
## Exposed variables
<div style={{textAlign: 'center'}}>
![ToolJet - List view widget](/img/widgets/kanban/variables.png)
</div>
| Variable | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| columns | The `columns` variable is an array of objects that includes the columns data in the respective objects. Since the columns variable is an array you'll need to specify the index of the object in the array to get the data within that object. Each object within a column has two keys - `id` and `title` and an array `cards` which is again an array of objects. Example: If you want to get the title of second card then you'll use `{{components.kanbanboard1.columns[1].title}}` - here we have specified the array index as `[1]` and then key which is the `title`. Similary you can get the card details using `{{components.kanbanboard1.columns[0].cards[1].title}}` |
| lastAddedCard | The variable `lastAddedCard` holds the properties of the card that has been added lastly. It holds the following data - `id`, `title`, and `columnId` of the last addded card. You can get the values using `{{components.kanbanboard1.lastAddedCard.title}}` |
| lastRemovedCard | The variable `lastRemovedCard` holds the properties of the card that has been recently deleted from the kanban. It holds the following data - `id`, `title`, and `columnId` of the recently deleted card. You can get the values using `{{components.kanbanboard1.lastRemovedCard.title}}` |
| lastCardMovement | The variable `lastCardMovement` holds the properties of the card that has been recently moved from its original position. It holds the following data - `originColumnId`, `destinationColumnId`, `originCardIndex`, `destinationCardIndex` and an object `cardDetails` which includes `title`. You can get the values using `{{components.kanbanboard1.lastCardMovement.cardDetails.title}}` or `{{components.kanbanboard1.lastCardMovement.destinationCardIndex}}` |
| lastUpdatedCard | The variable `lastUpdatedCard` holds `id`, `title`, and `columnId` of the latest modified card. You can get the values using `{{components.kanbanboard1.lastUpdatedCard.columnId}}` |
| selectedCard | The variable `selectedCard` holds `id`, `title`, `columnId`, and `description` of the selected card in the kanban. You can get the values using `{{components.kanbanboard1.selectedCard.description}}` |

View file

@ -16,9 +16,8 @@ const sidebars = {
label: 'Setup',
link: {type: 'doc', id: 'setup/index'},
items: [
'setup/introduction',
'setup/docker',
'setup/docker-local',
'setup/docker',
'setup/heroku',
'setup/ec2',
'setup/kubernetes',
@ -115,6 +114,7 @@ const sidebars = {
'widgets/file-picker',
'widgets/iframe',
'widgets/image',
'widgets/kanban',
'widgets/listview',
'widgets/map',
'widgets/modal',
@ -192,6 +192,7 @@ const sidebars = {
},
collapsed: true,
items: [
'user-authentication/user-lifecycle',
'user-authentication/general-settings',
'user-authentication/password-login',
{
@ -200,6 +201,22 @@ const sidebars = {
items: [
'user-authentication/sso/github',
'user-authentication/sso/google',
{
type: 'category',
label: 'OpenId Connect',
link: {
type: 'generated-index',
title: 'OpenId Connect',
description:" ",
keywords: ['okta','openid','azureAD'],
},
collapsed: false,
items: [
'user-authentication/sso/setup',
'user-authentication/sso/okta',
'user-authentication/sso/azuread',
],
}
],
},
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

BIN
docs/static/img/sso/azuread/azure-3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

BIN
docs/static/img/sso/azuread/azure7.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
docs/static/img/sso/azuread/azure8.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

BIN
docs/static/img/sso/azuread/azure9.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/static/img/sso/okta/create-app.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

BIN
docs/static/img/sso/openid/openid.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-new-section" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#9e9e9e" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="9" y1="12" x2="15" y2="12" />
<line x1="12" y1="9" x2="12" y2="15" />
<path d="M4 6v-1a1 1 0 0 1 1 -1h1m5 0h2m5 0h1a1 1 0 0 1 1 1v1m0 5v2m0 5v1a1 1 0 0 1 -1 1h-1m-5 0h-2m-5 0h-1a1 1 0 0 1 -1 -1v-1m0 -5v-2m0 -5" />
</svg>

After

Width:  |  Height:  |  Size: 518 B

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-pencil" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#597e8d" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M4 20h4l10.5 -10.5a1.5 1.5 0 0 0 -4 -4l-10.5 10.5v4"/>
<line x1="13.5" y1="6.5" x2="17.5" y2="10.5"/>
</svg>

After

Width:  |  Height:  |  Size: 405 B

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-html5" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path>
<path d="M15.5 8h-7l.5 4h6l-.5 3.5l-2.5 .75l-2.5 -.75l-.1 -.5"></path>
</svg>

After

Width:  |  Height:  |  Size: 428 B

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-board-split" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#597e8d" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<rect x="4" y="4" width="16" height="16" rx="2" />
<path d="M4 12h8" />
<path d="M12 15h8" />
<path d="M12 9h8" />
<path d="M12 4v16" />
</svg>

After

Width:  |  Height:  |  Size: 442 B

View file

@ -10,10 +10,12 @@ export default function GitSSOLoginButton({ configs }) {
});
};
return (
<div>
<div data-cy="git-tile">
<button onClick={gitLogin} className="btn border-0 rounded-2">
<img src="/assets/images/sso-buttons/git.svg" className="h-4" />
<span className="px-1">Sign in with Github</span>
<img src="/assets/images/sso-buttons/git.svg" className="h-4" data-cy="git-icon" />
<span className="px-1" data-cy="git-sign-in-text">
Sign in with Github
</span>
</button>
</div>
);

View file

@ -3,7 +3,7 @@ import GoogleLogin from 'react-google-login';
export default function GoogleSSOLoginButton(props) {
return (
<div className="mt-2">
<div className="mt-2" data-cy="google-sign-in-tile">
<GoogleLogin
clientId={props.configs?.client_id}
buttonText="Login"
@ -18,8 +18,11 @@ export default function GoogleSSOLoginButton(props) {
disabled={renderProps.disabled}
src="/assets/images/sso-buttons/google.svg"
className="h-4"
data-cy="google-icon"
/>
<span className="px-1">Sign in with Google</span>
<span className="px-1" data-cy="google-sign-in-text">
Sign in with Google
</span>
</button>
</div>
)}

View file

@ -52,6 +52,7 @@
"query-string": "^6.13.6",
"rc-slider": "^9.7.5",
"react": "^16.14.0",
"react-beautiful-dnd": "^13.1.0",
"react-big-calendar": "^0.38.0",
"react-bootstrap": "^1.5.2",
"react-circular-progressbar": "^2.0.4",
@ -17237,6 +17238,15 @@
"@types/node": "*"
}
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/html-minifier-terser": {
"version": "5.1.2",
"integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w=="
@ -17337,6 +17347,17 @@
"@types/react": "*"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.24",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.1",
"integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==",
@ -19386,6 +19407,14 @@
"urix": "^0.1.0"
}
},
"node_modules/css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"dependencies": {
"tiny-invariant": "^1.0.6"
}
},
"node_modules/css-loader": {
"version": "6.5.1",
"integrity": "sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==",
@ -28359,6 +28388,11 @@
"performance-now": "^2.1.0"
}
},
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
},
"node_modules/randombytes": {
"version": "2.1.0",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
@ -28519,6 +28553,24 @@
"pure-color": "^1.2.0"
}
},
"node_modules/react-beautiful-dnd": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
"dependencies": {
"@babel/runtime": "^7.9.2",
"css-box-model": "^1.2.0",
"memoize-one": "^5.1.1",
"raf-schd": "^4.0.2",
"react-redux": "^7.2.0",
"redux": "^4.0.4",
"use-memo-one": "^1.1.1"
},
"peerDependencies": {
"react": "^16.8.5 || ^17.0.0",
"react-dom": "^16.8.5 || ^17.0.0"
}
},
"node_modules/react-big-calendar": {
"version": "0.38.0",
"integrity": "sha512-eoVkt9gTo+f1HBL09+o7dYLxp6QxHv52fcn50P5PfaWp3S98uGLQqoqsvghT85koMKvGfDVa5V0+J7yHcaF07Q==",
@ -29079,6 +29131,46 @@
"react-dom": "~16"
}
},
"node_modules/react-redux": {
"version": "7.2.8",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
"integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==",
"dependencies": {
"@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^17.0.2"
},
"peerDependencies": {
"react": "^16.8.3 || ^17 || ^18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-redux/node_modules/@babel/runtime": {
"version": "7.17.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/react-redux/node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/react-rnd": {
"version": "10.3.0",
"integrity": "sha512-v+0TRPIaRWY25TYv02vLQHYpACbkX+4xKvsyIrUEy4bMpq0bP1oEiaxTorp0Xn72IVv0QZV1vOnZimgTEB/skw==",
@ -31033,6 +31125,14 @@
}
}
},
"node_modules/use-memo-one": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/util": {
"version": "0.10.3",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
@ -45089,6 +45189,15 @@
"@types/node": "*"
}
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/html-minifier-terser": {
"version": "5.1.2",
"integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w=="
@ -45189,6 +45298,17 @@
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.24",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-transition-group": {
"version": "4.4.1",
"integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==",
@ -46747,6 +46867,14 @@
"urix": "^0.1.0"
}
},
"css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"requires": {
"tiny-invariant": "^1.0.6"
}
},
"css-loader": {
"version": "6.5.1",
"integrity": "sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ==",
@ -53354,6 +53482,11 @@
"performance-now": "^2.1.0"
}
},
"raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
},
"randombytes": {
"version": "2.1.0",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
@ -53470,6 +53603,20 @@
"pure-color": "^1.2.0"
}
},
"react-beautiful-dnd": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz",
"integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==",
"requires": {
"@babel/runtime": "^7.9.2",
"css-box-model": "^1.2.0",
"memoize-one": "^5.1.1",
"raf-schd": "^4.0.2",
"react-redux": "^7.2.0",
"redux": "^4.0.4",
"use-memo-one": "^1.1.1"
}
},
"react-big-calendar": {
"version": "0.38.0",
"integrity": "sha512-eoVkt9gTo+f1HBL09+o7dYLxp6QxHv52fcn50P5PfaWp3S98uGLQqoqsvghT85koMKvGfDVa5V0+J7yHcaF07Q==",
@ -53884,6 +54031,34 @@
"webrtc-adapter": "^7.2.1"
}
},
"react-redux": {
"version": "7.2.8",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
"integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==",
"requires": {
"@babel/runtime": "^7.15.4",
"@types/react-redux": "^7.1.20",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^17.0.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.17.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}
}
},
"react-rnd": {
"version": "10.3.0",
"integrity": "sha512-v+0TRPIaRWY25TYv02vLQHYpACbkX+4xKvsyIrUEy4bMpq0bP1oEiaxTorp0Xn72IVv0QZV1vOnZimgTEB/skw==",
@ -55350,6 +55525,12 @@
"use-isomorphic-layout-effect": "^1.0.0"
}
},
"use-memo-one": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
"integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==",
"requires": {}
},
"util": {
"version": "0.10.3",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",

View file

@ -48,6 +48,7 @@
"query-string": "^6.13.6",
"rc-slider": "^9.7.5",
"react": "^16.14.0",
"react-beautiful-dnd": "^13.1.0",
"react-big-calendar": "^0.38.0",
"react-bootstrap": "^1.5.2",
"react-circular-progressbar": "^2.0.4",

View file

@ -44,7 +44,7 @@ class App extends React.Component {
if (this.state.currentUser) {
tooljetService.fetchMetaData().then((data) => {
this.setState({ onboarded: data.onboarded });
config.currentVersion = data.installed_version;
if (data.latest_version && lt(data.installed_version, data.latest_version) && data.version_ignored === false) {
this.setState({ updateAvailable: true });
}

View file

@ -92,9 +92,13 @@ class OrganizationInvitationPage extends React.Component {
</>
) : (
<>
<h2 className="card-title text-center mb-4">Set up your account</h2>
<h2 className="card-title text-center mb-4" data-cy="card-title">
Set up your account
</h2>
<div className="mb-3">
<label className="form-label">Password</label>
<label className="form-label" data-cy="password-label">
Password
</label>
<div className="input-group input-group-flat">
<input
onChange={this.handleChange}
@ -102,12 +106,15 @@ class OrganizationInvitationPage extends React.Component {
type="password"
className="form-control"
autoComplete="off"
data-cy="password-input"
/>
<span className="input-group-text"></span>
</div>
</div>
<div className="mb-3">
<label className="form-label">Confirm Password</label>
<label className="form-label" data-cy="confirm-password-label">
Confirm Password
</label>
<div className="input-group input-group-flat">
<input
onChange={this.handleChange}
@ -115,12 +122,13 @@ class OrganizationInvitationPage extends React.Component {
type="password"
className="form-control"
autoComplete="off"
data-cy="confirm-password-input"
/>
<span className="input-group-text"></span>
</div>
</div>
<div className="form-footer">
<p>
<p data-cy="terms-and-condition-info">
By clicking the button below, you agree to our{' '}
<a href="https://tooljet.io/terms">Terms and Conditions</a>.
</p>
@ -128,6 +136,7 @@ class OrganizationInvitationPage extends React.Component {
className={`btn mt-2 btn-primary w-100 ${isLoading ? ' btn-loading' : ''}`}
onClick={(e) => this.acceptInvite(e, true)}
disabled={isLoading}
data-cy="finish-setup-button"
>
Finish account setup and accept invite
</button>

View file

@ -38,10 +38,12 @@ import { renderTooltip } from '@/_helpers/appUtils';
import { RangeSlider } from './Components/RangeSlider';
import { Timeline } from './Components/Timeline';
import { SvgImage } from './Components/SvgImage';
import { Html } from './Components/Html';
import { ButtonGroup } from './Components/ButtonGroup';
import { CustomComponent } from './Components/CustomComponent/CustomComponent';
import { VerticalDivider } from './Components/verticalDivider';
import { PDF } from './Components/PDF';
import { KanbanBoard } from './Components/KanbanBoard/KanbanBoard';
import { Steps } from './Components/Steps';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import '@/_styles/custom.scss';
@ -87,10 +89,12 @@ const AllComponents = {
RangeSlider,
Timeline,
SvgImage,
Html,
ButtonGroup,
CustomComponent,
VerticalDivider,
PDF,
KanbanBoard,
Steps,
};

View file

@ -7,7 +7,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
}
return (
<div className="row">
<div className="row fx-container">
<div className="col">
<div className={`mb-3 field ${meta?.options?.className}`}>
<div style={{ display: 'flex', gap: 10 }}>
@ -98,7 +98,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
</div>
</div>
</div>
<div className="col-auto pt-0">
<div className="col-auto pt-0 style-fx fx-common">
<FxButton active={false} onPress={forceCodeBox} />
</div>
</div>

View file

@ -3,35 +3,25 @@ import 'react-datetime/css/react-datetime.css';
import { DateRangePicker } from 'react-dates';
import 'react-dates/lib/css/_datepicker.css';
import 'react-dates/initialize';
import { isEmpty } from 'lodash';
import moment from 'moment';
export const DaterangePicker = function DaterangePicker({
height,
properties,
styles,
exposedVariables,
setExposedVariable,
width,
}) {
export const DaterangePicker = function DaterangePicker({ height, properties, styles, setExposedVariable, width }) {
const { borderRadius, visibility, disabledState } = styles;
const { defaultStartDate, defaultEndDate } = properties;
const formatProp = properties.format;
// eslint-disable-next-line no-unused-vars
const startDateProp = isEmpty(exposedVariables.startDate)
? moment(defaultStartDate, formatProp)
: exposedVariables.startDate;
const endDateProp = isEmpty(exposedVariables.endDate) ? moment(defaultEndDate, formatProp) : exposedVariables.endDate;
const [focusedInput, setFocusedInput] = useState(null);
const [startDate, setStartDate] = useState(moment(defaultStartDate, formatProp));
const [endDate, setEndDate] = useState(endDateProp);
const [endDate, setEndDate] = useState(moment(defaultEndDate, formatProp));
const dateRangeRef = useRef(null);
useEffect(() => {
setStartDate(moment(defaultStartDate, formatProp));
setEndDate(moment(defaultEndDate, formatProp));
setExposedVariable('startDate', startDate.format(formatProp));
setExposedVariable('endDate', endDate.format(formatProp));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultEndDate, defaultStartDate, formatProp]);
useEffect(() => {

View file

@ -14,6 +14,8 @@ export const FilePicker = ({
styles,
}) => {
//* properties definitions
const instructionText =
component.definition.properties.instructionText?.value ?? 'Drag and Drop some files here, or click to select files';
const enableDropzone = component.definition.properties.enableDropzone.value ?? true;
const enablePicker = component.definition.properties?.enablePicker?.value ?? true;
const maxFileCount = component.definition.properties.maxFileCount?.value ?? 2;
@ -316,7 +318,7 @@ export const FilePicker = ({
) : (
<FilePicker.Signifiers
signifier={!isDragAccept && !accepted & !isDragReject}
feedback={'Drag & drop some files here, or click to select files'}
feedback={instructionText}
cls={`${darkMode ? 'text-secondary' : 'text-dark'} mt-3`}
/>
)}

View file

@ -0,0 +1,27 @@
import React, { useState, useEffect } from 'react';
import DOMPurify from 'dompurify';
export const Html = function ({ height, properties, styles, darkMode }) {
const { rawHtml: stringifyHTML } = properties;
const baseStyle = {
backgroundColor: darkMode ? '#47505D' : '#ffffff',
color: darkMode ? 'white' : 'black',
};
const { visibility } = styles;
const [rawHtml, setRawHtml] = useState('');
useEffect(() => {
setRawHtml(stringifyHTML);
}, [stringifyHTML]);
return (
<div style={{ display: visibility ? '' : 'none', width: '100%', height, overflowY: 'auto' }}>
{
<div
style={baseStyle}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(rawHtml, { FORCE_BODY: true }) }}
/>
}
</div>
);
};

View file

@ -0,0 +1,126 @@
import React from 'react';
import { DragDropContext } from 'react-beautiful-dnd';
import { v4 as uuidv4 } from 'uuid';
import Column from './Column';
import { reorderCards, moveCards } from './utils';
const grid = 8;
const getItemStyle = (isDragging, draggableStyle) => {
const _draggableStyle = isDragging
? { ...draggableStyle, left: draggableStyle.left - 100, top: draggableStyle.top - 100 }
: draggableStyle;
return {
..._draggableStyle,
userSelect: 'none',
padding: grid * 2,
margin: `0 0 ${grid}px 0`,
background: isDragging ? '#c2cfff' : '#fefefe',
};
};
function Board({ height, state, colStyles, setState, fireEvent, setExposedVariable }) {
const addNewItem = (state, keyIndex) => {
const newItem = {
id: uuidv4(),
title: 'New card',
columnId: state[keyIndex].id,
};
const newState = [...state];
if (!newState[keyIndex]['cards']) [(newState[keyIndex]['cards'] = [])];
newState[keyIndex]['cards'].push(newItem);
setState(newState);
setExposedVariable('lastAddedCard', newItem).then(() => fireEvent('onCardAdded'));
};
function onDragEnd(result) {
const { source, destination } = result;
// dropped outside the list
if (destination && destination !== null) {
const sInd = +source.droppableId;
const dInd = +destination.droppableId;
const originColumnId = state[sInd].id;
const destinationColumnId = state[dInd].id;
const card = state[sInd]['cards'][source.index];
const cardDetails = {
title: card.title,
};
if (sInd === dInd) {
const items = reorderCards(state[sInd]['cards'], source.index, destination.index);
const newState = [...state];
newState[sInd]['cards'] = items;
setState(newState);
} else {
const result = moveCards(state[sInd]['cards'], state[dInd].cards, source, destination);
const newState = [...state];
newState[sInd]['cards'] = result[sInd];
newState[dInd]['cards'] = result[dInd];
newState[dInd]['cards'][destination.index].columnId = newState[dInd].id;
setState(newState);
}
const movementDetails = {
originColumnId,
destinationColumnId,
originCardIndex: sInd,
destinationCardIndex: dInd,
cardDetails,
};
setExposedVariable('lastCardMovement', movementDetails).then(() => fireEvent('onCardMoved'));
}
}
const getListStyle = (isDraggingOver) => ({
...colStyles,
padding: grid,
borderColor: isDraggingOver && '#c0ccf8',
});
const updateCardProperty = (columnIndex, cardIndex, property, newValue) => {
const columnOfCardToBeUpdated = state[columnIndex];
const cardSetOfTheCardToBeUpdated = columnOfCardToBeUpdated.cards;
const cardToBeUpdated = cardSetOfTheCardToBeUpdated[cardIndex];
const updatedCard = { ...cardToBeUpdated, [property]: newValue };
const updatedCardSet = cardSetOfTheCardToBeUpdated.map((card, index) => (index === cardIndex ? updatedCard : card));
const updatedColumn = { ...columnOfCardToBeUpdated, cards: updatedCardSet };
const newState = state.map((column, index) => (index === columnIndex ? updatedColumn : column));
setState(newState);
setExposedVariable('lastUpdatedCard', updatedCard).then(() => fireEvent('onCardUpdated'));
};
return (
<div
style={{ height: height, overflowX: 'auto' }}
onMouseDown={(e) => e.stopPropagation()}
className="container d-flex"
>
<DragDropContext onDragEnd={onDragEnd}>
{state.map((col, ind) => (
<Column
key={ind}
state={state}
group={col}
keyIndex={ind}
getListStyle={getListStyle}
getItemStyle={getItemStyle}
updateCb={setState}
addNewItem={addNewItem}
colStyles={colStyles}
fireEvent={fireEvent}
setExposedVariable={setExposedVariable}
updateCardProperty={updateCardProperty}
boardHeight={height}
/>
))}
</DragDropContext>
</div>
);
}
export default Board;

View file

@ -0,0 +1,111 @@
import React from 'react';
import { Draggable } from 'react-beautiful-dnd';
import { BoardContext } from './KanbanBoard';
import { v4 as uuidv4 } from 'uuid';
import { CardEventPopover } from './CardPopover';
import { ReactPortal } from '@/_components/Portal/ReactPortal';
import _ from 'lodash';
export const Card = ({
item,
index,
state,
updateCb,
getItemStyle,
keyIndex,
fireEvent,
setExposedVariable,
updateCardProperty,
}) => {
const [isHovered, setIsHovered] = React.useState(false);
const [eventPopoverOptions, setEventPopoverOptions] = React.useState({ show: false });
function popoverClosed() {
setEventPopoverOptions({
...eventPopoverOptions,
show: false,
});
}
const { id, darkMode } = React.useContext(BoardContext);
const removeCardHandler = (colIndex, cardIndex) => {
const newState = [...state];
const removedCard = newState[colIndex]['cards'].splice(cardIndex, 1)[0];
updateCb(newState);
setExposedVariable('lastRemovedCard', removedCard).then(() => fireEvent('onCardRemoved'));
};
const draggableId = item.id ?? uuidv4();
const handleEventPopoverOptions = (e) => {
setEventPopoverOptions({
...eventPopoverOptions,
show: true,
offset: {
left: e.target.getBoundingClientRect().x,
top: e.target.getBoundingClientRect().y,
width: e.target.getBoundingClientRect().width,
height: e.target.getBoundingClientRect().height,
},
});
};
const handleCardClick = (event) => {
handleEventPopoverOptions(event);
setExposedVariable('selectedCard', item).then(() => fireEvent('onCardSelected'));
};
const target = React.useRef(null);
const el = document.getElementById(id);
return (
<Draggable
key={item.id}
draggableId={typeof draggableId !== String ? String(draggableId) : draggableId}
index={index}
>
{(dndProps, dndState) => (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={`dnd-card card card-sm ${darkMode && 'card-dark'}`}
ref={dndProps.innerRef}
{...dndProps.draggableProps}
{...dndProps.dragHandleProps}
style={{ ...getItemStyle(dndState.isDragging, dndProps.draggableProps.style) }}
>
<div className="card-body d-flex">
<span ref={target} onClick={handleCardClick} className="text-muted flex-grow-1 cursor-pointer fw-bold">
{item.title}
</span>
{isHovered && !item.isEditing && (
<span
className="cursor-pointer"
type="btn btn-sm btn-danger"
onClick={() => removeCardHandler(keyIndex, index)}
>
<img className="mx-1" src={`/assets/images/icons/trash.svg`} width={12} height={12} />
</span>
)}
{eventPopoverOptions.show && (
<ReactPortal parent={el} className="kanban-portal" componentName="kanban">
<CardEventPopover
kanbanCardWidgetId={id}
show={eventPopoverOptions.show}
offset={eventPopoverOptions.offset}
popoverClosed={popoverClosed}
card={item}
updateCardProperty={updateCardProperty}
index={index}
keyIndex={keyIndex}
/>
</ReactPortal>
)}
</div>
</div>
)}
</Draggable>
);
};

View file

@ -0,0 +1,157 @@
import React, { useEffect, useRef, useState } from 'react';
export const CardEventPopover = function ({
show,
offset,
kanbanCardWidgetId,
popoverClosed,
card,
updateCardProperty,
index,
keyIndex,
}) {
const parentRef = useRef(null);
const [showPopover, setShow] = useState(show);
const [top, setTop] = useState(0);
const [left, setLeft] = useState(0);
const [titleInputBoxValue, setTitleInputBoxValue] = useState(card.title ?? '');
const [descriptionTextAreaValue, setDescriptionTextAreaValue] = useState(card.description ?? '');
const [titleHovered, setTitleHovered] = useState(false);
const [descriptionHovered, setDescriptionHovered] = useState(false);
const [titleEditMode, setTitleEditMode] = useState(false);
const [descriptionEditMode, setDescriptionEditMode] = useState(false);
const minHeight = 400;
let kanbanBounds;
const kanbanElement = document.getElementById(kanbanCardWidgetId);
const handleClickOutside = (event) => {
if (parentRef.current && !parentRef.current.contains(event.target)) {
popoverClosed();
}
};
useEffect(() => {
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
});
useEffect(() => {
setShow(show);
}, [show]);
useEffect(() => {
if (offset?.top && showPopover) {
const _left = offset.left - kanbanBounds.x + offset.width;
const _top = ((offset.top - kanbanBounds.y) * 100) / kanbanBounds.height;
setTop(_top);
setLeft(_left);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [offset?.top, showPopover]);
if (kanbanElement && showPopover) {
kanbanBounds = kanbanElement.getBoundingClientRect();
}
const darkMode = localStorage.getItem('darkMode') === 'true';
return (
<div
style={{
position: 'absolute',
zIndex: 100,
width: '300px',
maxWidth: '300px',
height: 300,
top: `${top}%`,
left,
display: showPopover ? 'block' : 'none',
}}
role="tooltip"
x-placement="left"
className={`popover bs-popover-left shadow-lg ${darkMode && 'popover-dark-themed theme-dark'}`}
ref={parentRef}
id={`${kanbanCardWidgetId}-popover`}
>
{parentRef.current && showPopover && (
<div className="popover-body" style={{ padding: 'unset', width: '100%', height: 100, zIndex: 11 }}>
<div className="rows p-2 overflow-auto">
<div
className="row overflow-auto"
onMouseEnter={() => setTitleHovered(true)}
onMouseLeave={() => setTitleHovered(false)}
>
{titleEditMode ? (
<div>
<input
type="text"
className="form-control"
defaultValue={titleInputBoxValue}
onChange={(event) => setTitleInputBoxValue(event.target.value)}
onBlur={() => {
updateCardProperty(keyIndex, index, 'title', titleInputBoxValue);
setTitleEditMode(false);
}}
/>
</div>
) : (
<h3>
{card?.title ?? ''}
<img
src="/assets/images/icons/editor/edit.svg"
style={{ visibility: titleHovered ? 'visible' : 'hidden', height: 15, width: 15, paddingLeft: 1 }}
onClick={() => setTitleEditMode(true)}
/>
</h3>
)}
</div>
<div
className="row overflow-auto d-flex align-items-center flex-column"
onMouseEnter={() => setDescriptionHovered(true)}
onMouseLeave={() => setDescriptionHovered(false)}
style={{ maxHeight: 250 }}
>
{descriptionEditMode ? (
<textarea
className="form-control"
style={{ width: '95%' }}
onChange={(event) => setDescriptionTextAreaValue(event.target.value)}
onBlur={() => {
updateCardProperty(keyIndex, index, 'description', descriptionTextAreaValue);
setDescriptionEditMode(false);
}}
rows={10}
>
{descriptionTextAreaValue}
</textarea>
) : (
<p>
{['', undefined].includes(card.description) ? (
<a style={{ color: 'grey' }} onClick={() => setDescriptionEditMode(true)}>
Add description
</a>
) : (
card.description
)}
<img
src="/assets/images/icons/editor/edit.svg"
style={{
visibility: descriptionHovered ? 'visible' : 'hidden',
height: 15,
width: 15,
paddingLeft: 1,
}}
onClick={() => setDescriptionEditMode(true)}
/>
</p>
)}
</div>
</div>
</div>
)}
</div>
);
};

View file

@ -0,0 +1,128 @@
import React from 'react';
import { Droppable } from 'react-beautiful-dnd';
import { Card } from './Card';
import { BoardContext } from './KanbanBoard';
const Column = ({
state,
group,
keyIndex,
getListStyle,
getItemStyle,
updateCb,
addNewItem,
fireEvent,
setExposedVariable,
updateCardProperty,
boardHeight,
}) => {
const styles = {
overflowX: 'hidden',
overflowY: 'hidden',
maxHeight: boardHeight - 80,
};
const cards = group['cards'];
const updateGroupTitle = (newTitle) => {
const newState = [...state];
newState[keyIndex]['title'] = newTitle;
updateCb(newState);
};
const flipTitleToEditMode = (index) => {
const newState = [...state];
const isEditing = newState[index]['isEditing'];
if (isEditing === true) {
newState[index]['isEditing'] = false;
} else {
newState[index]['isEditing'] = true;
}
updateCb(newState);
};
const { enableAddCard, accentColor, darkMode } = React.useContext(BoardContext);
const hexaCodeToRgb = (hex) => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r},${g},${b},0.2)`;
};
const colAccentColor = {
color: accentColor ?? '#4d72fa',
backgroundColor: accentColor ? hexaCodeToRgb(accentColor) : hexaCodeToRgb('#4d72fa'),
};
return (
<Droppable key={keyIndex} droppableId={String(keyIndex)}>
{(dndProps, dndState) => (
<div
className={`card text-dark mb-3 m-2 kanban-column ${darkMode ? 'bg-dark' : 'bg-light'}`}
ref={dndProps.innerRef}
style={getListStyle(dndState.isDraggingOver)}
{...dndProps.droppableProps}
>
<div className="card-header d-flex">
<div className="flex-grow-1 ">
{group['isEditing'] ? (
<input
type="text"
className="form-control"
defaultValue={group['title']}
autoFocus={true}
onBlur={(e) => {
updateGroupTitle(e.target.value);
flipTitleToEditMode(keyIndex);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
updateGroupTitle(e.target.value);
flipTitleToEditMode(keyIndex);
}
}}
/>
) : (
<span
style={colAccentColor}
onClick={() => flipTitleToEditMode(keyIndex)}
className="bade-component cursor-text"
>
{group.title}
</span>
)}
</div>
</div>
<div style={{ ...styles }} className="card-body">
{cards?.map((item, index) => (
<Card
key={index}
item={item}
index={index}
state={state}
updateCb={updateCb}
getItemStyle={getItemStyle}
keyIndex={keyIndex}
fireEvent={fireEvent}
setExposedVariable={setExposedVariable}
updateCardProperty={updateCardProperty}
/>
))}
{dndProps.placeholder}
{enableAddCard && (
<button className="btn btn-primary w-100 add-card-btn" onClick={() => addNewItem(state, keyIndex)}>
Add card
</button>
)}
</div>
</div>
)}
</Droppable>
);
};
export default Column;

View file

@ -0,0 +1,131 @@
import _ from 'lodash';
import React from 'react';
import Board from './Board';
import { isCardColoumnIdUpdated, updateCardData, updateColumnData, getData, isArray, isValidCardData } from './utils';
export const BoardContext = React.createContext({});
export const KanbanBoard = ({
id,
height,
properties,
styles,
currentState,
setExposedVariable,
containerProps,
removeComponent,
fireEvent,
}) => {
const { columns, cardData, enableAddCard } = properties;
const { visibility, disabledState, width, minWidth, accentColor } = styles;
const [rawColumnData, setRawColumnData] = React.useState([]);
const [rawCardData, setRawCardData] = React.useState([]);
const [state, setState] = React.useState([]);
React.useEffect(() => {
setExposedVariable('columns', state);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state]);
React.useEffect(() => {
if (isArray(rawColumnData) || isArray(rawCardData)) {
const colData = JSON.parse(JSON.stringify(columns));
const _cardData = JSON.parse(JSON.stringify(cardData));
setRawColumnData(colData);
setRawCardData(_cardData);
const data = getData(colData, _cardData);
setState(data);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useEffect(() => {
if (JSON.stringify(columns) !== JSON.stringify(rawColumnData) && isArray(columns)) {
const newData = updateColumnData(state, rawColumnData, columns);
if (newData && isArray(newData)) {
setState(newData);
}
if (!newData && columns.length !== rawColumnData.length) {
setState(() => getData(columns, rawCardData));
}
setRawColumnData(columns);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns]);
React.useEffect(() => {
if (isValidCardData(cardData)) {
if (cardData.length !== rawCardData.length) {
setState(() => getData(columns, cardData));
} else if (JSON.stringify(cardData) !== JSON.stringify(rawCardData) && isArray(cardData)) {
if (cardData.length === 0) {
return;
}
const isColumnIdUpdated = isCardColoumnIdUpdated(rawCardData, cardData);
if (isColumnIdUpdated) {
const newData = getData(columns, cardData);
if (newData && isArray(newData)) {
setState(newData);
}
}
if (!isColumnIdUpdated) {
const newData = updateCardData(state, rawCardData, cardData);
if (newData && isArray(newData)) {
setState(newData);
}
if (newData === null) {
return setState(() => getData(columns, cardData));
}
}
}
setRawCardData(cardData);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cardData]);
const colStyles = {
width: !width ? '100%' : width,
minWidth: !minWidth ? '350px' : minWidth,
};
if (!state || state.length === 0) {
return (
<div className="mx-auto w-50 p-5 bg-light no-components-box" style={{ marginTop: '15%' }}>
<center className="text-muted">Board is empty.</center>
</div>
);
}
const darkMode = localStorage.getItem('darkMode') === 'true';
return (
<BoardContext.Provider
value={{ id, currentState, enableAddCard, accentColor, containerProps, removeComponent, darkMode }}
>
<div
id={id}
style={{ display: visibility ? '' : 'none' }}
data-disabled={disabledState}
className={`kanban-container p-0 ${darkMode ? 'dark-themed' : ''}`}
>
<Board
height={height}
state={state}
isDisable={disabledState}
colStyles={colStyles}
setState={setState}
fireEvent={fireEvent}
setExposedVariable={setExposedVariable}
/>
</div>
</BoardContext.Provider>
);
};

View file

@ -0,0 +1,140 @@
import _ from 'lodash';
export const getData = (columns, cards) => {
if (isArray(cards) && isArray(columns)) {
const clonedColumns = [...columns];
cards.forEach((card) => {
const column = clonedColumns.find((column) => column.id === card.columnId);
if (column) {
column['cards'] = column?.cards ? [...column.cards, card] : [card];
}
});
return clonedColumns;
}
return null;
};
export const reorderCards = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
export const moveCards = (source, destination, droppableSource, droppableDestination) => {
const sourceClone = Array.from(source);
const destinationClone = destination ? Array.from(destination) : [];
const [removed] = sourceClone.splice(droppableSource.index, 1);
destinationClone.splice(droppableDestination.index, 0, removed);
const result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destinationClone;
return result;
};
const diffCol = (next, current) => {
const nextState = [...next];
const currentState = [...current];
const diff = [];
nextState.forEach((col, index) => {
const curr = col;
const next = currentState[index];
const isDiff = curr.id === next?.id && curr.title === next.title;
if (!isDiff && next) {
const newCol = {
...next,
id: curr.id,
title: curr.title,
};
diff.push(newCol);
}
});
return diff;
};
export const updateColumnData = (currentData, column, newData) => {
const diff = diffCol(newData, currentData);
if (diff.length === 0) return null;
const nextState = [...currentData];
diff.forEach((col) => {
const index = nextState.findIndex((c) => c.id === col.id);
nextState[index] = col;
});
return nextState;
};
const cardDiffExits = (currentCards, newCards, state) => {
const diff = [];
if (!currentCards) return null;
newCards.forEach((card) => {
const index = currentCards.findIndex((c) => c.id === card.id);
const updatedColumnId = findCard(state, card.id)?.columnId;
if (index !== -1) {
const newCard = {
...card,
columnId: updatedColumnId,
};
diff.push(newCard);
}
});
return diff;
};
export const updateCardData = (currentData, cards, newData) => {
const diffing = cardDiffExits(cards, newData, currentData);
if (!diffing || diffing.length === 0) return null;
const newState = [...currentData];
diffing.forEach((card) => {
const colIndex = newState.findIndex((c) => c.id === card.columnId);
const cardIndex = newState[colIndex].cards.findIndex((c) => c.id === card.id);
newState[colIndex].cards[cardIndex] = card;
});
return newState;
};
const findCard = (state, cardId) => {
for (let i = 0; i < state.length; i++) {
for (let j = 0; j < state[i].cards?.length ?? 0; j++) {
if (state[i].cards[j].id === cardId) {
return state[i].cards[j];
}
}
}
};
export const isCardColoumnIdUpdated = (currentCardData, nextCardData) => {
const currentState = [...currentCardData];
const nextState = [...nextCardData];
let isColoumnIdUpdated = false;
currentState.forEach((card, index) => {
if (nextState[index]) {
const prevColId = card.columnId;
const newColId = nextState[index].columnId;
if (prevColId !== newColId) {
isColoumnIdUpdated = true;
}
}
});
return isColoumnIdUpdated;
};
export const isArray = (value) => Object.prototype.toString.call(value).slice(8, -1) === 'Array';
export const isValidCardData = (cardData) => {
return _.isArray(cardData) && cardData.every((card) => _.isString(card.id));
};

View file

@ -18,12 +18,13 @@ export const Listview = function Listview({
const fallbackStyles = { visibility: true, disabledState: false };
const { data, rowHeight, showBorder } = { ...fallbackProperties, ...properties };
const { backgroundColor, visibility, disabledState } = { ...fallbackStyles, ...styles };
const { backgroundColor, visibility, disabledState, borderRadius } = { ...fallbackStyles, ...styles };
const computedStyles = {
backgroundColor,
height,
display: visibility ? 'flex' : 'none',
borderRadius: borderRadius ?? 0,
};
const onRowClicked = (index) => {

View file

@ -1,4 +1,5 @@
import React, { useState, useCallback, useRef, useEffect } from 'react';
// eslint-disable-next-line import/no-unresolved
import { Document, Page } from 'react-pdf/dist/esm/entry.webpack';
export const PDF = React.memo(({ styles, properties, width, height }) => {

View file

@ -18,7 +18,8 @@ export const Steps = function Button({ properties, styles, fireEvent, setExposed
useEffect(() => {
setActiveStep(currentStep);
setExposedVariable('currentStepId', currentStep).then(() => fireEvent('onSelect'));
setExposedVariable('currentStepId', currentStep);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentStep]);
return (

View file

@ -720,6 +720,7 @@ export function Table({
() => [...leftActionsCellData, ...columnData, ...rightActionsCellData],
[
JSON.stringify(columnData),
JSON.stringify(tableData),
JSON.stringify(actions),
leftActionsCellData.length,
rightActionsCellData.length,

View file

@ -236,7 +236,7 @@ export const DraggableBox = function DraggableBox({
setDragging(false);
onDragStop(e, id, direction, currentLayout, currentLayoutOptions);
}}
cancel={`div.table-responsive.jet-data-table, div.calendar-widget, div.text-input, .textarea, .map-widget, .range-slider`}
cancel={`div.table-responsive.jet-data-table, div.calendar-widget, div.text-input, .textarea, .map-widget, .range-slider, .kanban-container`}
onDragStart={(e) => e.stopPropagation()}
onResizeStop={(e, direction, ref, d, position) => {
setResizing(false);

View file

@ -49,8 +49,8 @@ import { createWebsocketConnection } from '@/_helpers/websocketConnection';
import Tooltip from 'react-bootstrap/Tooltip';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import RealtimeAvatars from './RealtimeAvatars';
import InitVersionCreateModal from './InitVersionCreateModal';
import RealtimeCursors from '@/Editor/RealtimeCursors';
import { initEditorWalkThrough } from '@/_helpers/createWalkThrough';
setAutoFreeze(false);
enablePatches();
@ -90,6 +90,8 @@ class Editor extends React.Component {
},
};
this.dataSourceModalRef = React.createRef();
this.state = {
currentUser: authenticationService.currentUserValue,
app: {},
@ -127,7 +129,6 @@ class Editor extends React.Component {
isDeletingDataQuery: false,
showHiddenOptionsForDataQueryId: null,
showQueryConfirmation: false,
showInitVersionCreateModal: false,
showCreateVersionModalPrompt: false,
isSourceSelected: false,
isSaving: false,
@ -361,7 +362,7 @@ class Editor extends React.Component {
fetchApp = () => {
const appId = this.props.match.params.id;
appService.getApp(appId).then((data) => {
appService.getApp(appId).then(async (data) => {
const dataDefinition = defaults(data.definition, this.defaultDefinition);
this.setState(
{
@ -371,10 +372,8 @@ class Editor extends React.Component {
appDefinition: dataDefinition,
slug: data.slug,
},
() => {
this.setState({
showInitVersionCreateModal: isEmpty(this.state.editingVersion),
});
async () => {
if (isEmpty(this.state.editingVersion)) await this.createInitVersion(appId);
computeComponentState(this, this.state.appDefinition.components).then(() => {
this.runQueries(data.data_queries);
@ -388,6 +387,18 @@ class Editor extends React.Component {
});
};
createInitVersion = async (appId) => {
return appVersionService
.create(appId, 'v1')
.then(() => {
initEditorWalkThrough();
this.fetchApp();
})
.catch((err) => {
toast.success(err?.error ?? 'Version creation failed');
});
};
setAppDefinitionFromVersion = (version) => {
this.appDefinitionChanged(defaults(version.definition, this.defaultDefinition), {
skipAutoSave: true,
@ -1072,6 +1083,12 @@ class Editor extends React.Component {
handleEvent = (eventName, options) => onEvent(this, eventName, options, 'edit');
runQuery = (queryId, queryName) => runQuery(this, queryId, queryName);
dataSourceModalHandler = () => {
this.dataSourceModalRef.current.dataSourceModalToggleStateHandler();
};
render() {
const {
currentSidebarTab,
@ -1115,7 +1132,7 @@ class Editor extends React.Component {
{/* This is for viewer to show query confirmations */}
<Confirm
show={showQueryConfirmation}
message={'Do you want to run this query?'}
message={`Do you want to run this query - ${this.state.queryConfirmationData?.queryName}?`}
onConfirm={(queryConfirmationData) => onQueryConfirm(this, queryConfirmationData)}
onCancel={() => onQueryCancel(this)}
queryConfirmationData={this.state.queryConfirmationData}
@ -1149,6 +1166,7 @@ class Editor extends React.Component {
onBlur={(e) => this.saveAppName(this.state.app.id, e.target.value)}
className="form-control-plaintext form-control-plaintext-sm"
value={this.state.app.name}
data-cy="app-name-input"
/>
<span className="input-icon-addon">
<EditIcon />
@ -1163,7 +1181,13 @@ class Editor extends React.Component {
})}
data-cy="autosave-indicator"
>
{this.state.isSaving ? <Spinner size="small" /> : 'All changes are saved'}
{this.state.isSaving ? (
<Spinner size="small" />
) : this.state.saveError ? (
'Could not save changes'
) : (
'All changes are saved'
)}
</span>
{config.ENABLE_MULTIPLAYER_EDITING && <RealtimeAvatars />}
{editingVersion && (
@ -1241,6 +1265,7 @@ class Editor extends React.Component {
runQuery={(queryId, queryName) => runQuery(this, queryId, queryName)}
toggleAppMaintenance={this.toggleAppMaintenance}
is_maintenance_on={this.state.app.is_maintenance_on}
ref={this.dataSourceModalRef}
isSaving={this.state.isSaving}
isUnsavedQueriesAvailable={this.state.isUnsavedQueriesAvailable}
/>
@ -1465,6 +1490,8 @@ class Editor extends React.Component {
allComponents={appDefinition.components}
isSourceSelected={this.state.isSourceSelected}
isQueryPaneDragging={this.state.isQueryPaneDragging}
runQuery={this.runQuery}
dataSourceModalHandler={this.dataSourceModalHandler}
setStateOfUnsavedQueries={this.setStateOfUnsavedQueries}
/>
</div>
@ -1576,13 +1603,6 @@ class Editor extends React.Component {
/>
)}
</div>
<InitVersionCreateModal
showModal={this.state.showInitVersionCreateModal}
hideModal={() => this.setState({ showInitVersionCreateModal: false })}
fetchApp={this.fetchApp}
darkMode={this.props.darkMode}
appId={this.state.appId}
/>
</DndProvider>
</div>
);

View file

@ -1,81 +0,0 @@
import React from 'react';
import cx from 'classnames';
// eslint-disable-next-line import/no-named-as-default
import toast from 'react-hot-toast';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import { isEmpty } from 'lodash';
import { appVersionService } from '@/_services';
import { initEditorWalkThrough } from '@/_helpers/createWalkThrough';
const InitVersionCreateModal = ({ appId, showModal, hideModal, fetchApp, darkMode }) => {
const [initVersionName, setInitVersionName] = React.useState('v1');
const [isCreatingInitVersion, setIsCreatingInitVersion] = React.useState(false);
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
// eslint-disable-next-line no-undef
createInitVersion();
}
};
const createInitVersion = async () => {
if (!isEmpty(initVersionName?.trim())) {
setIsCreatingInitVersion(true);
await appVersionService.create(appId, initVersionName);
setIsCreatingInitVersion(false);
initEditorWalkThrough();
fetchApp();
hideModal();
toast.success('Version Created');
} else {
setIsCreatingInitVersion(false);
toast.error('The name of version should not be empty');
}
};
return (
<Modal
contentClassName={darkMode ? 'theme-dark' : ''}
show={showModal}
size="md"
backdrop="static"
keyboard={true}
enforceFocus={false}
animation={false}
centered={true}
>
<Modal.Header>
<Modal.Title>Create Version</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="row m-2">
<div className="col">
<input
type="text"
className="form-control"
placeholder="version name"
defaultValue={initVersionName}
onChange={(e) => setInitVersionName(e.target.value)}
onKeyPress={(e) => handleKeyPress(e)}
autoFocus={true}
/>
</div>
</div>
<div className="row m-2">
<div className="col">
<small className="muted">Create a version to start building your app</small>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button className={cx({ 'btn-loading': isCreatingInitVersion })} onClick={() => createInitVersion()}>
Create
</Button>
</Modal.Footer>
</Modal>
);
};
export default InitVersionCreateModal;

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