Merge branch 'develop' into bugfix/signup-email-case-sensitive-issue
2
.version
|
|
@ -1 +1 @@
|
|||
1.17.1
|
||||
1.19.0
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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())}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
};
|
||||
|
|
@ -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]",
|
||||
}
|
||||
32
cypress/constants/selectors/manageGroups.js
Normal 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]",
|
||||
};
|
||||
29
cypress/constants/selectors/manageSSO.js
Normal 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)",
|
||||
};
|
||||
|
|
@ -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]",
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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",
|
||||
};
|
||||
|
|
@ -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={
|
||||
|
|
|
|||
31
cypress/constants/texts/manageGroups.js
Normal 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",
|
||||
};
|
||||
31
cypress/constants/texts/manageSSO.js
Normal 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",
|
||||
};
|
||||
|
|
@ -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."
|
||||
|
||||
}
|
||||
70
cypress/integration/dashboard/manageGroups.spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
})
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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();
|
||||
}
|
||||
}))
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
@ -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 );
|
||||
};
|
||||
75
cypress/support/utils/manageGroups.js
Normal 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();
|
||||
};
|
||||
132
cypress/support/utils/manageSSO.js
Normal 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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Insert Record
|
||||
- To insert a record.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Delete Record
|
||||
- To delete a record.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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.
|
||||
|
|
@ -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'}}>
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Select `General Settings`
|
||||
- Select `General Settings`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||

|
||||
|
||||
</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.
|
||||
|
|
|
|||
67
docs/docs/user-authentication/sso/azuread.md
Normal 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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Select `new registration`
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Click on `Add certificate or secret` next to the **Client credentials**.
|
||||
|
||||
- Click on `+New Client Secret`
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Give a description, set the expiry, and then click on the `Add` button.
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
54
docs/docs/user-authentication/sso/okta.md
Normal 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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Select `Sign-in method` as `OIDC - OpenID Connect` and `Application type` as `Web Application`. Go to the next step
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Enter `App integration name` and then enter `Sign-in redirect URIs` as `<YOUR-DOMAIN>/sso/okta`.
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Create application and configure `Client Credentials` in the UI.
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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
|
||||
:::
|
||||
35
docs/docs/user-authentication/sso/setup.md
Normal 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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Select `OpenId Connect`.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Find and set **Name**, **Client Id**, **Client Secret**, and **Well Known URL** from your Open Id provider.
|
||||
158
docs/docs/user-authentication/user-lifecycle.md
Normal 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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- User with admin privileges can invite members
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Invited user will receive welcome email with activation URL, unregistered user can follow the link and setup Tooljet account
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Invited user can onboard through SSO login, without using an invitation link
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Archive user
|
||||
- User can be archived by workspace admin from using `Manage User` page
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Unarchive user
|
||||
- User can be unArchived by workspace admin from using `Manage User` page
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- Users can be added to multiple workspaces. Users can create their own workspaces and manage them.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Archive user
|
||||
- User can be archived by workspace admin from using `Manage User` page
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Unarchive user
|
||||
- User can be unarchive by workspace admin from using `Manage User` page
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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
|
|
@ -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'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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.
|
||||
:::
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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'}}>
|
||||
|
||||

|
||||
|
||||
</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}}` |
|
||||
|
|
@ -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',
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
BIN
docs/static/img/datasource-reference/bigquery/bq-delete.png
vendored
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
docs/static/img/datasource-reference/bigquery/bq-insert.png
vendored
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/static/img/datasource-reference/bigquery/bq-update.png
vendored
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
docs/static/img/datasource-reference/bigquery/bq-view.png
vendored
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
docs/static/img/sso/azuread/azure-3.png
vendored
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
docs/static/img/sso/azuread/azure-4-cred.png
vendored
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
docs/static/img/sso/azuread/azure-5-client-id.png
vendored
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
docs/static/img/sso/azuread/azure-app-reg.png
vendored
Normal file
|
After Width: | Height: | Size: 345 KiB |
BIN
docs/static/img/sso/azuread/azure7.png
vendored
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/static/img/sso/azuread/azure8.png
vendored
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
docs/static/img/sso/azuread/azure9.png
vendored
Normal file
|
After Width: | Height: | Size: 290 KiB |
BIN
docs/static/img/sso/azuread/select-new-reg-azure.png
vendored
Normal file
|
After Width: | Height: | Size: 328 KiB |
BIN
docs/static/img/sso/okta/create-app-s1.png
vendored
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/static/img/sso/okta/create-app-s2.png
vendored
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
docs/static/img/sso/okta/create-app-s3.png
vendored
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
docs/static/img/sso/okta/create-app-s4.png
vendored
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
docs/static/img/sso/okta/create-app-s5.png
vendored
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
docs/static/img/sso/okta/create-app.png
vendored
Normal file
|
After Width: | Height: | Size: 267 KiB |
BIN
docs/static/img/sso/openid/openid-select.png
vendored
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
docs/static/img/sso/openid/openid.png
vendored
Normal file
|
After Width: | Height: | Size: 838 KiB |
BIN
docs/static/img/user-lifecycle/accept-invite-sw.png
vendored
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/static/img/user-lifecycle/archive-user.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/static/img/user-lifecycle/invite-link-mw.png
vendored
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
docs/static/img/user-lifecycle/signup-mw.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/static/img/user-lifecycle/single-ws-signup.png
vendored
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/static/img/user-lifecycle/sso-enable-signup-sw.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/static/img/user-lifecycle/sso-onboard-sw.png
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/static/img/user-lifecycle/switch.png
vendored
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/static/img/user-lifecycle/unarchive-sw.png
vendored
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
docs/static/img/user-lifecycle/unarchive-user-mw.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/static/img/user-lifecycle/user-invite-sw.png
vendored
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/static/img/user-lifecycle/user-mw.png
vendored
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
docs/static/img/widgets/kanban/kanban-events.png
vendored
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/static/img/widgets/kanban/kanban.png
vendored
Normal file
|
After Width: | Height: | Size: 301 KiB |
BIN
docs/static/img/widgets/kanban/layout.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/static/img/widgets/kanban/properties.png
vendored
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/static/img/widgets/kanban/styles.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/static/img/widgets/kanban/variables.png
vendored
Normal file
|
After Width: | Height: | Size: 87 KiB |
6
frontend/assets/images/icons/add-source.svg
Normal 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 |
5
frontend/assets/images/icons/editor/edit.svg
Normal 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 |
5
frontend/assets/images/icons/widgets/Html.svg
Normal 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 |
8
frontend/assets/images/icons/widgets/kanbanboard.svg
Normal 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 |
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
181
frontend/package-lock.json
generated
|
|
@ -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=",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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`}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
27
frontend/src/Editor/Components/Html.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
126
frontend/src/Editor/Components/KanbanBoard/Board.jsx
Normal 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;
|
||||
111
frontend/src/Editor/Components/KanbanBoard/Card.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
157
frontend/src/Editor/Components/KanbanBoard/CardPopover.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
128
frontend/src/Editor/Components/KanbanBoard/Column.jsx
Normal 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;
|
||||
131
frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
140
frontend/src/Editor/Components/KanbanBoard/utils.js
Normal 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));
|
||||
};
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -720,6 +720,7 @@ export function Table({
|
|||
() => [...leftActionsCellData, ...columnData, ...rightActionsCellData],
|
||||
[
|
||||
JSON.stringify(columnData),
|
||||
JSON.stringify(tableData),
|
||||
JSON.stringify(actions),
|
||||
leftActionsCellData.length,
|
||||
rightActionsCellData.length,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||