diff --git a/.version b/.version index 511a76e6fa..815d5ca06d 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -1.17.1 +1.19.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 12c78c50ae..53412e0cdb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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) diff --git a/cli/README.md b/cli/README.md index a49488fa73..e739f1b5c4 100644 --- a/cli/README.md +++ b/cli/README.md @@ -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 +* [`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 [--build] diff --git a/cli/package-lock.json b/cli/package-lock.json index 1b18c5224c..b0f8118789 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -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", diff --git a/cli/package.json b/cli/package.json index aee3794335..5f1eec09a8 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,7 +1,7 @@ { "name": "@tooljet/cli", "description": "tooljet cli tool", - "version": "0.0.10", + "version": "0.0.12", "bin": { "tooljet": "./bin/run" }, diff --git a/cli/src/commands/info.ts b/cli/src/commands/info.ts new file mode 100644 index 0000000000..fc25ac37ea --- /dev/null +++ b/cli/src/commands/info.ts @@ -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())} + `); + } +} diff --git a/cypress/constants/selectors/button.js b/cypress/constants/selectors/button.js index 4bf81994bb..b8b7106c39 100644 --- a/cypress/constants/selectors/button.js +++ b/cypress/constants/selectors/button.js @@ -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" }; \ No newline at end of file diff --git a/cypress/constants/selectors/common.js b/cypress/constants/selectors/common.js index d878a51d9c..3bde85a8c0 100644 --- a/cypress/constants/selectors/common.js +++ b/cypress/constants/selectors/common.js @@ -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]", } \ No newline at end of file diff --git a/cypress/constants/selectors/manageGroups.js b/cypress/constants/selectors/manageGroups.js new file mode 100644 index 0000000000..8a1b9499b2 --- /dev/null +++ b/cypress/constants/selectors/manageGroups.js @@ -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]", +}; \ No newline at end of file diff --git a/cypress/constants/selectors/manageSSO.js b/cypress/constants/selectors/manageSSO.js new file mode 100644 index 0000000000..0e70a77a54 --- /dev/null +++ b/cypress/constants/selectors/manageSSO.js @@ -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)", +}; \ No newline at end of file diff --git a/cypress/constants/selectors/manageUsers.js b/cypress/constants/selectors/manageUsers.js index afa14767fb..2eb74b06aa 100644 --- a/cypress/constants/selectors/manageUsers.js +++ b/cypress/constants/selectors/manageUsers.js @@ -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]", + } + } \ No newline at end of file diff --git a/cypress/constants/texts/button.js b/cypress/constants/texts/button.js index 008ecd2075..3069895e45 100644 --- a/cypress/constants/texts/button.js +++ b/cypress/constants/texts/button.js @@ -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", }; \ No newline at end of file diff --git a/cypress/constants/texts/common.js b/cypress/constants/texts/common.js index d9f7da8d27..ede148ea6a 100644 --- a/cypress/constants/texts/common.js +++ b/cypress/constants/texts/common.js @@ -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={ diff --git a/cypress/constants/texts/manageGroups.js b/cypress/constants/texts/manageGroups.js new file mode 100644 index 0000000000..5640db6111 --- /dev/null +++ b/cypress/constants/texts/manageGroups.js @@ -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", +}; \ No newline at end of file diff --git a/cypress/constants/texts/manageSSO.js b/cypress/constants/texts/manageSSO.js new file mode 100644 index 0000000000..da1e9d43af --- /dev/null +++ b/cypress/constants/texts/manageSSO.js @@ -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", +}; \ No newline at end of file diff --git a/cypress/constants/texts/manageUsers.js b/cypress/constants/texts/manageUsers.js index 2f41ff8df5..8addde6efb 100644 --- a/cypress/constants/texts/manageUsers.js +++ b/cypress/constants/texts/manageUsers.js @@ -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." + } \ No newline at end of file diff --git a/cypress/integration/dashboard/manageGroups.spec.js b/cypress/integration/dashboard/manageGroups.spec.js new file mode 100644 index 0000000000..f5df2d13b9 --- /dev/null +++ b/cypress/integration/dashboard/manageGroups.spec.js @@ -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(); + }); +}); \ No newline at end of file diff --git a/cypress/integration/dashboard/manageUsers.spec.js b/cypress/integration/dashboard/multi-workspace/manageUsers.spec.js similarity index 100% rename from cypress/integration/dashboard/manageUsers.spec.js rename to cypress/integration/dashboard/multi-workspace/manageUsers.spec.js diff --git a/cypress/integration/dashboard/single-workspace/manageSSO.spec.js b/cypress/integration/dashboard/single-workspace/manageSSO.spec.js new file mode 100644 index 0000000000..f7271c189a --- /dev/null +++ b/cypress/integration/dashboard/single-workspace/manageSSO.spec.js @@ -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(); + }) +}); \ No newline at end of file diff --git a/cypress/integration/dashboard/single-workspace/manageUsers.spec.js b/cypress/integration/dashboard/single-workspace/manageUsers.spec.js new file mode 100644 index 0000000000..2e408781c0 --- /dev/null +++ b/cypress/integration/dashboard/single-workspace/manageUsers.spec.js @@ -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); + }); + }); +}); \ No newline at end of file diff --git a/cypress/integration/editor/widget/button.spec.js b/cypress/integration/editor/widget/button.spec.js index 5b5cd7adf0..8efda1780c 100644 --- a/cypress/integration/editor/widget/button.spec.js +++ b/cypress/integration/editor/widget/button.spec.js @@ -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(); + }); + }); \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 1e75412739..265ad396f8 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -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(); } })) }); diff --git a/cypress/support/utils/button.js b/cypress/support/utils/button.js index be916ac771..07f7a5fd15 100644 --- a/cypress/support/utils/button.js +++ b/cypress/support/utils/button.js @@ -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(); }; \ No newline at end of file diff --git a/cypress/support/utils/common.js b/cypress/support/utils/common.js index f953e59a26..394767cdf1 100644 --- a/cypress/support/utils/common.js +++ b/cypress/support/utils/common.js @@ -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 ); }; \ No newline at end of file diff --git a/cypress/support/utils/manageGroups.js b/cypress/support/utils/manageGroups.js new file mode 100644 index 0000000000..35e9dc0568 --- /dev/null +++ b/cypress/support/utils/manageGroups.js @@ -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(); +}; \ No newline at end of file diff --git a/cypress/support/utils/manageSSO.js b/cypress/support/utils/manageSSO.js new file mode 100644 index 0000000000..d0262152e4 --- /dev/null +++ b/cypress/support/utils/manageSSO.js @@ -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(); + } + }); +}; + diff --git a/docs/docs/data-sources/bigquery.md b/docs/docs/data-sources/bigquery.md index 1b51599ecc..0d5a10681b 100644 --- a/docs/docs/data-sources/bigquery.md +++ b/docs/docs/data-sources/bigquery.md @@ -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)). +
![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/query.png) -
\ No newline at end of file + + +### Insert Record +- To insert a record. + +
+ +![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-insert.png) + +
+ +### Delete Record +- To delete a record. + +
+ +![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-delete.png) + +
+ +:::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. + +
+ +![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-update.png) + +
+ +:::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. + +
+ +![ToolJet - Data source - BigQuery](/img/datasource-reference/bigquery/bq-view.png) + +
+ + +### 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. \ No newline at end of file diff --git a/docs/docs/user-authentication/general-settings.md b/docs/docs/user-authentication/general-settings.md index c16f45d836..11e048c918 100644 --- a/docs/docs/user-authentication/general-settings.md +++ b/docs/docs/user-authentication/general-settings.md @@ -5,21 +5,21 @@ title: General Settings # Single Sign-On General Settings -Select `Manage SSO` from workspace options +- Select `Manage SSO` from workspace options -
+
-![ToolJet - SSO configs](/img/password-login/organization-menu.png) + ![ToolJet - SSO configs](/img/password-login/organization-menu.png) -
+
-Select `General Settings` +- Select `General Settings` -
+
-![ToolJet - SSO configs](/img/sso/general/general-settings.png) + ![ToolJet - SSO configs](/img/sso/general/general-settings.png) -
+
## 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. diff --git a/docs/docs/user-authentication/sso/azuread.md b/docs/docs/user-authentication/sso/azuread.md new file mode 100644 index 0000000000..1818900092 --- /dev/null +++ b/docs/docs/user-authentication/sso/azuread.md @@ -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` +
+ + ![ToolJet - AzureAD app registration](/img/sso/azuread/azure-app-reg.png) + +
+ +- Select `new registration` +
+ + ![ToolJet - AzureAD app registration](/img/sso/azuread/select-new-reg-azure.png) + +
+ +- 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`. +
+ + ![ToolJet - AzureAD app registration](/img/sso/azuread/azure-3.png) + +
+ +- Application will be registered and will be able to view the details + +- Configure Application (Client) ID as `client id` in Open Id configuration page. +
+ + ![ToolJet - AzureAD app registration](/img/sso/azuread/azure-4-cred.png) + +
+ +- Click on `Add certificate or secret` next to the **Client credentials**. + +- Click on `+New Client Secret` +
+ + ![ToolJet - AzureAD app registration](/img/sso/azuread/azure8.png) + +
+ +- Give a description, set the expiry, and then click on the `Add` button. +
+ + ![ToolJet - AzureAD app registration](/img/sso/azuread/azure7.png) + +
+ +- 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. +
+ + ![ToolJet - AzureAD app registration](/img/sso/azuread/azure9.png) + +
diff --git a/docs/docs/user-authentication/sso/okta.md b/docs/docs/user-authentication/sso/okta.md new file mode 100644 index 0000000000..5fede822dd --- /dev/null +++ b/docs/docs/user-authentication/sso/okta.md @@ -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` +
+ + ![ToolJet - Okta create application](/img/sso/okta/create-app.png) + +
+ +- Select `Sign-in method` as `OIDC - OpenID Connect` and `Application type` as `Web Application`. Go to the next step +
+ + ![ToolJet - Okta create application](/img/sso/okta/create-app-s1.png) + +
+ +- Enter `App integration name` and then enter `Sign-in redirect URIs` as `/sso/okta`. +
+ + ![ToolJet - Okta create application](/img/sso/okta/create-app-s2.png) + +
+ +- Create application and configure `Client Credentials` in the UI. +
+ + ![ToolJet - Okta create application](/img/sso/okta/create-app-s4.png) + +
+ +- 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)`. + +
+ +![ToolJet - Okta create application](/img/sso/okta/create-app-s5.png) + +
+ +:::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 +::: \ No newline at end of file diff --git a/docs/docs/user-authentication/sso/setup.md b/docs/docs/user-authentication/sso/setup.md new file mode 100644 index 0000000000..c567229c1f --- /dev/null +++ b/docs/docs/user-authentication/sso/setup.md @@ -0,0 +1,35 @@ +--- +id: setup +title: Setup +--- + +# Configure OpenId Connect Single Sign-on + +:::info +NOTE: This feature is available only for Enterprise edition. +::: + + +- Select `Manage SSO` from workspace options + +
+ + ![ToolJet - SSO configs](/img/password-login/organization-menu.png) + +
+ +- Select `OpenId Connect`. + +
+ + ![ToolJet - SSO configs](/img/sso/openid/openid-select.png) + +
+ +
+ +![ToolJet - SSO configs](/img/sso/openid/openid.png) + +
+ +- Find and set **Name**, **Client Id**, **Client Secret**, and **Well Known URL** from your Open Id provider. \ No newline at end of file diff --git a/docs/docs/user-authentication/user-lifecycle.md b/docs/docs/user-authentication/user-lifecycle.md new file mode 100644 index 0000000000..1248f56c02 --- /dev/null +++ b/docs/docs/user-authentication/user-lifecycle.md @@ -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. + +
+ + ![ToolJet - Single-Workspace sign up](/img/user-lifecycle/single-ws-signup.png) + +
+ +- User with admin privileges can invite members + +
+ + ![ToolJet - Single-Workspace invite user](/img/user-lifecycle/user-invite-sw.png) + +
+ +- Invited user will receive welcome email with activation URL, unregistered user can follow the link and setup Tooljet account + +
+ + ![ToolJet - Single-Workspace accept invite](/img/user-lifecycle/accept-invite-sw.png) + +
+ +- Invited user can onboard through SSO login, without using an invitation link + +
+ + ![ToolJet - Single-Workspace accept invite](/img/user-lifecycle/sso-onboard-sw.png) + +
+ +- If `enable signup` option in enabled in SSO general settings, user can setup account through SSO login without an invite + +
+ + ![ToolJet - Single-Workspace sign up using SSO](/img/user-lifecycle/sso-enable-signup-sw.png) + +
+ +### Archive user + - User can be archived by workspace admin from using `Manage User` page + +
+ + ![ToolJet - Single-Workspace Archive user](/img/user-lifecycle/archive-user.png) + +
+ +### Unarchive user + - User can be unArchived by workspace admin from using `Manage User` page + +
+ + ![ToolJet - Single-Workspace Unarchive user](/img/user-lifecycle/unarchive-sw.png) + +
+ + :::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. + +
+ + ![ToolJet - Multi-Workspace sign up](/img/user-lifecycle/signup-mw.png) + +
+ + - Users can be added to multiple workspaces. Users can create their own workspaces and manage them. + +
+ + ![ToolJet - Multi-Workspace sign up](/img/user-lifecycle/user-mw.png) + +
+ + - 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. + +
+ + ![ToolJet - Multi-Workspace sign up](/img/user-lifecycle/invite-link-mw.png) + +
+ + - Invited user can onboard through SSO login, without using an invitation link from the workspace [login page](/docs/user-authentication/general-settings#login-url) + +
+ + ![ToolJet - Single-Workspace accept invite](/img/user-lifecycle/sso-onboard-sw.png) + +
+ + - 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) + +
+ + ![ToolJet - Single-Workspace sign up using SSO](/img/user-lifecycle/sso-enable-signup-sw.png) + +
+ +### Archive user + - User can be archived by workspace admin from using `Manage User` page + +
+ + ![ToolJet - Single-Workspace Archive user](/img/user-lifecycle/archive-user.png) + +
+ +### Unarchive user + - User can be unarchive by workspace admin from using `Manage User` page + +
+ + ![ToolJet - Single-Workspace Unarchive user](/img/user-lifecycle/unarchive-user-mw.png) + +
+ + :::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 + +
+ + ![ToolJet - Single-Workspace sign up using SSO](/img/user-lifecycle/switch.png) + +
+ + +## 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 | \ No newline at end of file diff --git a/docs/docs/widgets/kanban.md b/docs/docs/widgets/kanban.md new file mode 100644 index 0000000000..a3b56fd173 --- /dev/null +++ b/docs/docs/widgets/kanban.md @@ -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. + +
+ +![ToolJet - Kanban widget](/img/widgets/kanban/kanban.png) + +
+ +## 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. + +
+ +![ToolJet - Kanban widget](/img/widgets/kanban/kanban-events.png) + +
+ +## Properties + +
+ +:::caution +Please keep in mind that you need to provide an `id` for each card in the `Card data` field
+and this `id` must be of type string. +::: + +![ToolJet - Kanban widget](/img/widgets/kanban/properties.png) + +
+ +| 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 + +Tooltip: Set a tooltip text to specify the information about the data/kanban when the user moves the mouse pointer over the widget. + +## Layout + +
+ +![ToolJet - Kanban widget](/img/widgets/kanban/layout.png) + +
+ +| 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 + +
+ +![ToolJet - List view widget](/img/widgets/kanban/styles.png) + +
+ +| 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 + +
+ +![ToolJet - List view widget](/img/widgets/kanban/variables.png) + +
+ +| 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}}` | diff --git a/docs/sidebars.js b/docs/sidebars.js index da8b0bb3e5..c393fee5d9 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -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', + ], + } ], }, ], diff --git a/docs/static/img/datasource-reference/bigquery/bq-delete.png b/docs/static/img/datasource-reference/bigquery/bq-delete.png new file mode 100644 index 0000000000..045dd2b0ea Binary files /dev/null and b/docs/static/img/datasource-reference/bigquery/bq-delete.png differ diff --git a/docs/static/img/datasource-reference/bigquery/bq-insert.png b/docs/static/img/datasource-reference/bigquery/bq-insert.png new file mode 100644 index 0000000000..5995190bef Binary files /dev/null and b/docs/static/img/datasource-reference/bigquery/bq-insert.png differ diff --git a/docs/static/img/datasource-reference/bigquery/bq-update.png b/docs/static/img/datasource-reference/bigquery/bq-update.png new file mode 100644 index 0000000000..6b341ae9b3 Binary files /dev/null and b/docs/static/img/datasource-reference/bigquery/bq-update.png differ diff --git a/docs/static/img/datasource-reference/bigquery/bq-view.png b/docs/static/img/datasource-reference/bigquery/bq-view.png new file mode 100644 index 0000000000..6c684e0f36 Binary files /dev/null and b/docs/static/img/datasource-reference/bigquery/bq-view.png differ diff --git a/docs/static/img/sso/azuread/azure-3.png b/docs/static/img/sso/azuread/azure-3.png new file mode 100644 index 0000000000..d4328d05d3 Binary files /dev/null and b/docs/static/img/sso/azuread/azure-3.png differ diff --git a/docs/static/img/sso/azuread/azure-4-cred.png b/docs/static/img/sso/azuread/azure-4-cred.png new file mode 100644 index 0000000000..58277d0c95 Binary files /dev/null and b/docs/static/img/sso/azuread/azure-4-cred.png differ diff --git a/docs/static/img/sso/azuread/azure-5-client-id.png b/docs/static/img/sso/azuread/azure-5-client-id.png new file mode 100644 index 0000000000..b20bba7f0e Binary files /dev/null and b/docs/static/img/sso/azuread/azure-5-client-id.png differ diff --git a/docs/static/img/sso/azuread/azure-app-reg.png b/docs/static/img/sso/azuread/azure-app-reg.png new file mode 100644 index 0000000000..271a32fda9 Binary files /dev/null and b/docs/static/img/sso/azuread/azure-app-reg.png differ diff --git a/docs/static/img/sso/azuread/azure7.png b/docs/static/img/sso/azuread/azure7.png new file mode 100644 index 0000000000..2f161a41ef Binary files /dev/null and b/docs/static/img/sso/azuread/azure7.png differ diff --git a/docs/static/img/sso/azuread/azure8.png b/docs/static/img/sso/azuread/azure8.png new file mode 100644 index 0000000000..d00091c275 Binary files /dev/null and b/docs/static/img/sso/azuread/azure8.png differ diff --git a/docs/static/img/sso/azuread/azure9.png b/docs/static/img/sso/azuread/azure9.png new file mode 100644 index 0000000000..5e46c27d5b Binary files /dev/null and b/docs/static/img/sso/azuread/azure9.png differ diff --git a/docs/static/img/sso/azuread/select-new-reg-azure.png b/docs/static/img/sso/azuread/select-new-reg-azure.png new file mode 100644 index 0000000000..a824e6588a Binary files /dev/null and b/docs/static/img/sso/azuread/select-new-reg-azure.png differ diff --git a/docs/static/img/sso/okta/create-app-s1.png b/docs/static/img/sso/okta/create-app-s1.png new file mode 100644 index 0000000000..01a5d1fb02 Binary files /dev/null and b/docs/static/img/sso/okta/create-app-s1.png differ diff --git a/docs/static/img/sso/okta/create-app-s2.png b/docs/static/img/sso/okta/create-app-s2.png new file mode 100644 index 0000000000..c9e3152c17 Binary files /dev/null and b/docs/static/img/sso/okta/create-app-s2.png differ diff --git a/docs/static/img/sso/okta/create-app-s3.png b/docs/static/img/sso/okta/create-app-s3.png new file mode 100644 index 0000000000..a45ef737cc Binary files /dev/null and b/docs/static/img/sso/okta/create-app-s3.png differ diff --git a/docs/static/img/sso/okta/create-app-s4.png b/docs/static/img/sso/okta/create-app-s4.png new file mode 100644 index 0000000000..c437aec5c1 Binary files /dev/null and b/docs/static/img/sso/okta/create-app-s4.png differ diff --git a/docs/static/img/sso/okta/create-app-s5.png b/docs/static/img/sso/okta/create-app-s5.png new file mode 100644 index 0000000000..9423944320 Binary files /dev/null and b/docs/static/img/sso/okta/create-app-s5.png differ diff --git a/docs/static/img/sso/okta/create-app.png b/docs/static/img/sso/okta/create-app.png new file mode 100644 index 0000000000..2ca39534b4 Binary files /dev/null and b/docs/static/img/sso/okta/create-app.png differ diff --git a/docs/static/img/sso/openid/openid-select.png b/docs/static/img/sso/openid/openid-select.png new file mode 100644 index 0000000000..4cb3107936 Binary files /dev/null and b/docs/static/img/sso/openid/openid-select.png differ diff --git a/docs/static/img/sso/openid/openid.png b/docs/static/img/sso/openid/openid.png new file mode 100644 index 0000000000..59bb61605d Binary files /dev/null and b/docs/static/img/sso/openid/openid.png differ diff --git a/docs/static/img/user-lifecycle/accept-invite-sw.png b/docs/static/img/user-lifecycle/accept-invite-sw.png new file mode 100644 index 0000000000..7700fe8839 Binary files /dev/null and b/docs/static/img/user-lifecycle/accept-invite-sw.png differ diff --git a/docs/static/img/user-lifecycle/archive-user.png b/docs/static/img/user-lifecycle/archive-user.png new file mode 100644 index 0000000000..0eb8ae52bd Binary files /dev/null and b/docs/static/img/user-lifecycle/archive-user.png differ diff --git a/docs/static/img/user-lifecycle/invite-link-mw.png b/docs/static/img/user-lifecycle/invite-link-mw.png new file mode 100644 index 0000000000..7b9ac3fb70 Binary files /dev/null and b/docs/static/img/user-lifecycle/invite-link-mw.png differ diff --git a/docs/static/img/user-lifecycle/signup-mw.png b/docs/static/img/user-lifecycle/signup-mw.png new file mode 100644 index 0000000000..a37b248322 Binary files /dev/null and b/docs/static/img/user-lifecycle/signup-mw.png differ diff --git a/docs/static/img/user-lifecycle/single-ws-signup.png b/docs/static/img/user-lifecycle/single-ws-signup.png new file mode 100644 index 0000000000..6427803b3f Binary files /dev/null and b/docs/static/img/user-lifecycle/single-ws-signup.png differ diff --git a/docs/static/img/user-lifecycle/sso-enable-signup-sw.png b/docs/static/img/user-lifecycle/sso-enable-signup-sw.png new file mode 100644 index 0000000000..271fd824e0 Binary files /dev/null and b/docs/static/img/user-lifecycle/sso-enable-signup-sw.png differ diff --git a/docs/static/img/user-lifecycle/sso-onboard-sw.png b/docs/static/img/user-lifecycle/sso-onboard-sw.png new file mode 100644 index 0000000000..fcecc60a20 Binary files /dev/null and b/docs/static/img/user-lifecycle/sso-onboard-sw.png differ diff --git a/docs/static/img/user-lifecycle/switch.png b/docs/static/img/user-lifecycle/switch.png new file mode 100644 index 0000000000..49be78fb53 Binary files /dev/null and b/docs/static/img/user-lifecycle/switch.png differ diff --git a/docs/static/img/user-lifecycle/unarchive-sw.png b/docs/static/img/user-lifecycle/unarchive-sw.png new file mode 100644 index 0000000000..2fa0af93fb Binary files /dev/null and b/docs/static/img/user-lifecycle/unarchive-sw.png differ diff --git a/docs/static/img/user-lifecycle/unarchive-user-mw.png b/docs/static/img/user-lifecycle/unarchive-user-mw.png new file mode 100644 index 0000000000..e17cb2af89 Binary files /dev/null and b/docs/static/img/user-lifecycle/unarchive-user-mw.png differ diff --git a/docs/static/img/user-lifecycle/user-invite-sw.png b/docs/static/img/user-lifecycle/user-invite-sw.png new file mode 100644 index 0000000000..3b76217628 Binary files /dev/null and b/docs/static/img/user-lifecycle/user-invite-sw.png differ diff --git a/docs/static/img/user-lifecycle/user-mw.png b/docs/static/img/user-lifecycle/user-mw.png new file mode 100644 index 0000000000..b77e87b745 Binary files /dev/null and b/docs/static/img/user-lifecycle/user-mw.png differ diff --git a/docs/static/img/widgets/kanban/kanban-events.png b/docs/static/img/widgets/kanban/kanban-events.png new file mode 100644 index 0000000000..37f96ad0f8 Binary files /dev/null and b/docs/static/img/widgets/kanban/kanban-events.png differ diff --git a/docs/static/img/widgets/kanban/kanban.png b/docs/static/img/widgets/kanban/kanban.png new file mode 100644 index 0000000000..00d7be5f29 Binary files /dev/null and b/docs/static/img/widgets/kanban/kanban.png differ diff --git a/docs/static/img/widgets/kanban/layout.png b/docs/static/img/widgets/kanban/layout.png new file mode 100644 index 0000000000..c16fcd0450 Binary files /dev/null and b/docs/static/img/widgets/kanban/layout.png differ diff --git a/docs/static/img/widgets/kanban/properties.png b/docs/static/img/widgets/kanban/properties.png new file mode 100644 index 0000000000..1288ab5541 Binary files /dev/null and b/docs/static/img/widgets/kanban/properties.png differ diff --git a/docs/static/img/widgets/kanban/styles.png b/docs/static/img/widgets/kanban/styles.png new file mode 100644 index 0000000000..01c5ffced2 Binary files /dev/null and b/docs/static/img/widgets/kanban/styles.png differ diff --git a/docs/static/img/widgets/kanban/variables.png b/docs/static/img/widgets/kanban/variables.png new file mode 100644 index 0000000000..eefe9b47ed Binary files /dev/null and b/docs/static/img/widgets/kanban/variables.png differ diff --git a/frontend/assets/images/icons/add-source.svg b/frontend/assets/images/icons/add-source.svg new file mode 100644 index 0000000000..ea62a3f266 --- /dev/null +++ b/frontend/assets/images/icons/add-source.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/editor/edit.svg b/frontend/assets/images/icons/editor/edit.svg new file mode 100644 index 0000000000..8a90d97d5b --- /dev/null +++ b/frontend/assets/images/icons/editor/edit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/assets/images/icons/widgets/Html.svg b/frontend/assets/images/icons/widgets/Html.svg new file mode 100644 index 0000000000..ff563b5f18 --- /dev/null +++ b/frontend/assets/images/icons/widgets/Html.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/widgets/kanbanboard.svg b/frontend/assets/images/icons/widgets/kanbanboard.svg new file mode 100644 index 0000000000..b826e73a7a --- /dev/null +++ b/frontend/assets/images/icons/widgets/kanbanboard.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/frontend/ee/components/LoginPage/GitSSOLoginButton.jsx b/frontend/ee/components/LoginPage/GitSSOLoginButton.jsx index 08af60c678..06d71efb27 100644 --- a/frontend/ee/components/LoginPage/GitSSOLoginButton.jsx +++ b/frontend/ee/components/LoginPage/GitSSOLoginButton.jsx @@ -10,10 +10,12 @@ export default function GitSSOLoginButton({ configs }) { }); }; return ( -
+
); diff --git a/frontend/ee/components/LoginPage/GoogleSSOLoginButton.jsx b/frontend/ee/components/LoginPage/GoogleSSOLoginButton.jsx index 3de6f1becb..3aaa30e43e 100644 --- a/frontend/ee/components/LoginPage/GoogleSSOLoginButton.jsx +++ b/frontend/ee/components/LoginPage/GoogleSSOLoginButton.jsx @@ -3,7 +3,7 @@ import GoogleLogin from 'react-google-login'; export default function GoogleSSOLoginButton(props) { return ( -
+
- Sign in with Google + + Sign in with Google +
)} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4efa2f30a7..87712f01e2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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=", diff --git a/frontend/package.json b/frontend/package.json index 01b39179d1..0b111ce953 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index fd373c0905..2d52cb563e 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -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 }); } diff --git a/frontend/src/ConfirmationPage/OrganizationInvitationPage.jsx b/frontend/src/ConfirmationPage/OrganizationInvitationPage.jsx index 6f24bb7486..f52b64d2cc 100644 --- a/frontend/src/ConfirmationPage/OrganizationInvitationPage.jsx +++ b/frontend/src/ConfirmationPage/OrganizationInvitationPage.jsx @@ -92,9 +92,13 @@ class OrganizationInvitationPage extends React.Component { ) : ( <> -

Set up your account

+

+ Set up your account +

- +
- +
-

+

By clicking the button below, you agree to our{' '} Terms and Conditions.

@@ -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 diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index 957398c9a3..6ad9aea037 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -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, }; diff --git a/frontend/src/Editor/CodeBuilder/Elements/AlignButtons.jsx b/frontend/src/Editor/CodeBuilder/Elements/AlignButtons.jsx index f43cf34c11..a7cc6991ea 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/AlignButtons.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/AlignButtons.jsx @@ -7,7 +7,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => { } return ( -
+
@@ -98,7 +98,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
-
+
diff --git a/frontend/src/Editor/Components/DaterangePicker.jsx b/frontend/src/Editor/Components/DaterangePicker.jsx index 4d6e81e24d..7fb9c6da7d 100644 --- a/frontend/src/Editor/Components/DaterangePicker.jsx +++ b/frontend/src/Editor/Components/DaterangePicker.jsx @@ -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(() => { diff --git a/frontend/src/Editor/Components/FilePicker.jsx b/frontend/src/Editor/Components/FilePicker.jsx index dcdffb956d..1f31f375ad 100644 --- a/frontend/src/Editor/Components/FilePicker.jsx +++ b/frontend/src/Editor/Components/FilePicker.jsx @@ -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 = ({ ) : ( )} diff --git a/frontend/src/Editor/Components/Html.jsx b/frontend/src/Editor/Components/Html.jsx new file mode 100644 index 0000000000..f271461f82 --- /dev/null +++ b/frontend/src/Editor/Components/Html.jsx @@ -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 ( +
+ { +
+ } +
+ ); +}; diff --git a/frontend/src/Editor/Components/KanbanBoard/Board.jsx b/frontend/src/Editor/Components/KanbanBoard/Board.jsx new file mode 100644 index 0000000000..e1557cf194 --- /dev/null +++ b/frontend/src/Editor/Components/KanbanBoard/Board.jsx @@ -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 ( +
e.stopPropagation()} + className="container d-flex" + > + + {state.map((col, ind) => ( + + ))} + +
+ ); +} + +export default Board; diff --git a/frontend/src/Editor/Components/KanbanBoard/Card.jsx b/frontend/src/Editor/Components/KanbanBoard/Card.jsx new file mode 100644 index 0000000000..b28d4801cc --- /dev/null +++ b/frontend/src/Editor/Components/KanbanBoard/Card.jsx @@ -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 ( + + {(dndProps, dndState) => ( +
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) }} + > +
+ + {item.title} + + {isHovered && !item.isEditing && ( + removeCardHandler(keyIndex, index)} + > + + + )} + {eventPopoverOptions.show && ( + + + + )} +
+
+ )} +
+ ); +}; diff --git a/frontend/src/Editor/Components/KanbanBoard/CardPopover.jsx b/frontend/src/Editor/Components/KanbanBoard/CardPopover.jsx new file mode 100644 index 0000000000..a017dfb70f --- /dev/null +++ b/frontend/src/Editor/Components/KanbanBoard/CardPopover.jsx @@ -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 ( + + ); +}; diff --git a/frontend/src/Editor/Components/KanbanBoard/Column.jsx b/frontend/src/Editor/Components/KanbanBoard/Column.jsx new file mode 100644 index 0000000000..8c3faa2267 --- /dev/null +++ b/frontend/src/Editor/Components/KanbanBoard/Column.jsx @@ -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 ( + + {(dndProps, dndState) => ( +
+
+
+ {group['isEditing'] ? ( + { + updateGroupTitle(e.target.value); + flipTitleToEditMode(keyIndex); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + updateGroupTitle(e.target.value); + flipTitleToEditMode(keyIndex); + } + }} + /> + ) : ( + flipTitleToEditMode(keyIndex)} + className="bade-component cursor-text" + > + {group.title} + + )} +
+
+
+ {cards?.map((item, index) => ( + + ))} + + {dndProps.placeholder} + {enableAddCard && ( + + )} +
+
+ )} +
+ ); +}; + +export default Column; diff --git a/frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx b/frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx new file mode 100644 index 0000000000..3428351f63 --- /dev/null +++ b/frontend/src/Editor/Components/KanbanBoard/KanbanBoard.jsx @@ -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 ( +
+
Board is empty.
+
+ ); + } + const darkMode = localStorage.getItem('darkMode') === 'true'; + return ( + +
+ +
+
+ ); +}; diff --git a/frontend/src/Editor/Components/KanbanBoard/utils.js b/frontend/src/Editor/Components/KanbanBoard/utils.js new file mode 100644 index 0000000000..65631fd92a --- /dev/null +++ b/frontend/src/Editor/Components/KanbanBoard/utils.js @@ -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)); +}; diff --git a/frontend/src/Editor/Components/Listview.jsx b/frontend/src/Editor/Components/Listview.jsx index 9a08da09ed..038edef7ed 100644 --- a/frontend/src/Editor/Components/Listview.jsx +++ b/frontend/src/Editor/Components/Listview.jsx @@ -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) => { diff --git a/frontend/src/Editor/Components/PDF.jsx b/frontend/src/Editor/Components/PDF.jsx index de749705f4..31a940ea00 100644 --- a/frontend/src/Editor/Components/PDF.jsx +++ b/frontend/src/Editor/Components/PDF.jsx @@ -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 }) => { diff --git a/frontend/src/Editor/Components/Steps.jsx b/frontend/src/Editor/Components/Steps.jsx index 7c89d88e48..cabc5183f6 100644 --- a/frontend/src/Editor/Components/Steps.jsx +++ b/frontend/src/Editor/Components/Steps.jsx @@ -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 ( diff --git a/frontend/src/Editor/Components/Table/Table.jsx b/frontend/src/Editor/Components/Table/Table.jsx index 985438e52f..1f28870c8b 100644 --- a/frontend/src/Editor/Components/Table/Table.jsx +++ b/frontend/src/Editor/Components/Table/Table.jsx @@ -720,6 +720,7 @@ export function Table({ () => [...leftActionsCellData, ...columnData, ...rightActionsCellData], [ JSON.stringify(columnData), + JSON.stringify(tableData), JSON.stringify(actions), leftActionsCellData.length, rightActionsCellData.length, diff --git a/frontend/src/Editor/DraggableBox.jsx b/frontend/src/Editor/DraggableBox.jsx index 6a634fe6ef..c759290b0a 100644 --- a/frontend/src/Editor/DraggableBox.jsx +++ b/frontend/src/Editor/DraggableBox.jsx @@ -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); diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 05d700919d..fc034cef99 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -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 */} 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" /> @@ -1163,7 +1181,13 @@ class Editor extends React.Component { })} data-cy="autosave-indicator" > - {this.state.isSaving ? : 'All changes are saved'} + {this.state.isSaving ? ( + + ) : this.state.saveError ? ( + 'Could not save changes' + ) : ( + 'All changes are saved' + )} {config.ENABLE_MULTIPLAYER_EDITING && } {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} />
@@ -1576,13 +1603,6 @@ class Editor extends React.Component { /> )}
- this.setState({ showInitVersionCreateModal: false })} - fetchApp={this.fetchApp} - darkMode={this.props.darkMode} - appId={this.state.appId} - />
); diff --git a/frontend/src/Editor/InitVersionCreateModal.jsx b/frontend/src/Editor/InitVersionCreateModal.jsx deleted file mode 100644 index 8d149a3a24..0000000000 --- a/frontend/src/Editor/InitVersionCreateModal.jsx +++ /dev/null @@ -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 ( - - - Create Version - - -
-
- setInitVersionName(e.target.value)} - onKeyPress={(e) => handleKeyPress(e)} - autoFocus={true} - /> -
-
- -
-
- Create a version to start building your app -
-
-
- - - -
- ); -}; - -export default InitVersionCreateModal; diff --git a/frontend/src/Editor/LeftSidebar/SidebarDatasources.jsx b/frontend/src/Editor/LeftSidebar/SidebarDatasources.jsx index 9ffd6f071e..1b59166126 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarDatasources.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarDatasources.jsx @@ -18,9 +18,10 @@ export const LeftSidebarDataSources = ({ dataSources = [], dataSourcesChanged, dataQueriesChanged, + toggleDataSourceManagerModal, + showDataSourceManagerModal, }) => { const [open, trigger, content] = usePopover(false); - const [showDataSourceManagerModal, toggleDataSourceManagerModal] = React.useState(false); const [selectedDataSource, setSelectedDataSource] = React.useState(null); const [isDeleteModalVisible, setDeleteModalVisibility] = React.useState(false); const [isDeletingDatasource, setDeletingDatasource] = React.useState(false); diff --git a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx index e3be97ce8b..e33193fd04 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx +++ b/frontend/src/Editor/LeftSidebar/SidebarInspector.jsx @@ -135,7 +135,7 @@ export const LeftSidebarInspector = ({
{ +export const LeftSidebar = forwardRef((props, ref) => { const router = useRouter(); + const { + appId, + switchDarkMode, + darkMode = false, + components, + toggleComments, + dataSources = [], + dataSourcesChanged, + dataQueriesChanged, + errorLogs, + appVersionsId, + globalSettingsChanged, + globalSettings, + currentState, + appDefinition, + setSelectedComponent, + removeComponent, + runQuery, + toggleAppMaintenance, + is_maintenance_on, + isSaving, + isUnsavedQueriesAvailable, + } = props; const [showLeaveDialog, setShowLeaveDialog] = useState(false); + const [showDataSourceManagerModal, toggleDataSourceManagerModal] = useState(false); + + useImperativeHandle(ref, () => ({ + dataSourceModalToggleStateHandler() { + toggleDataSourceManagerModal(true); + }, + })); return (
{config.COMMENT_FEATURE_ENABLE && ( @@ -79,6 +89,7 @@ export const LeftSidebar = ({ icon="back" className="left-sidebar-item no-border left-sidebar-layout" text={'Back'} + data-cy="back-button" />
); -}; +}); diff --git a/frontend/src/Editor/QueryManager/DataSourceLister.jsx b/frontend/src/Editor/QueryManager/DataSourceLister.jsx index b3fa1ac75c..35ea3fef67 100644 --- a/frontend/src/Editor/QueryManager/DataSourceLister.jsx +++ b/frontend/src/Editor/QueryManager/DataSourceLister.jsx @@ -1,8 +1,16 @@ import React, { useState } from 'react'; import { allSvgs } from '@tooljet/plugins/client'; import RunjsIcon from '../Icons/runjs.svg'; +import AddIcon from '../../../assets/images/icons/add-source.svg'; -function DataSourceLister({ dataSources, staticDataSources, changeDataSource, handleBackButton, darkMode }) { +function DataSourceLister({ + dataSources, + staticDataSources, + changeDataSource, + handleBackButton, + darkMode, + dataSourceModalHandler, +}) { const [allSources] = useState([...dataSources, ...staticDataSources]); const computedStyles = { @@ -34,6 +42,10 @@ function DataSourceLister({ dataSources, staticDataSources, changeDataSource, ha
); })} +
+ +

Add datasource

+
); } diff --git a/frontend/src/Editor/QueryManager/QueryManager.jsx b/frontend/src/Editor/QueryManager/QueryManager.jsx index 1e23e1d0e2..c7bfbeb0a0 100644 --- a/frontend/src/Editor/QueryManager/QueryManager.jsx +++ b/frontend/src/Editor/QueryManager/QueryManager.jsx @@ -13,7 +13,8 @@ import Preview from './Preview'; import DataSourceLister from './DataSourceLister'; import { allSvgs } from '@tooljet/plugins/client'; // import { Confirm } from '../Viewer/Confirm'; -import _ from 'lodash'; +import _, { isEmpty, isEqual } from 'lodash'; +import { Button, ButtonGroup, Dropdown } from 'react-bootstrap'; const queryNameRegex = new RegExp('^[A-Za-z0-9_-]*$'); @@ -39,9 +40,11 @@ let QueryManager = class QueryManager extends React.Component { showSaveConfirmation: false, restArrayValuesChanged: false, nextProps: null, + buttonText: '', }; this.previewPanelRef = React.createRef(); + this.buttonConfig = JSON.parse(localStorage.getItem('queryManagerButtonConfig')); } setStateFromProps = (props) => { @@ -90,6 +93,14 @@ let QueryManager = class QueryManager extends React.Component { base0E: '#d381c3', base0F: '#be643c', }, + buttonText: + props.mode === 'edit' + ? this.buttonConfig?.editMode?.text ?? 'Save & Run' + : this.buttonConfig?.createMode?.text ?? 'Create & Run', + shouldRunQuery: + props.mode === 'edit' + ? this.buttonConfig?.editMode?.shouldRunQuery ?? true + : this.buttonConfig?.createMode?.shouldRunQuery ?? true, }, () => { if (this.props.mode === 'edit') { @@ -145,6 +156,26 @@ let QueryManager = class QueryManager extends React.Component { // } // } // } + if (!isEmpty(this.state.updatedQuery)) { + const query = nextProps.dataQueries.find((q) => q.id === this.state.updatedQuery.id); + if (query) { + const isLoading = nextProps.currentState?.queries[query.name] + ? nextProps.currentState?.queries[query.name]?.isLoading + : false; + const prevLoading = this.state.currentState?.queries[query.name] + ? this.state.currentState?.queries[query.name]?.isLoading + : false; + if (!isEmpty(nextProps.selectedQuery) && !isEqual(this.state.selectedQuery, nextProps.selectedQuery)) { + if (query && !isLoading && !prevLoading) { + this.props.runQuery(query.id, query.name); + } + } else if (!isLoading && prevLoading) { + this.state.updatedQuery.updateQuery + ? this.setState({ updatedQuery: {}, isUpdating: false }) + : this.setState({ updatedQuery: {}, isCreating: false }); + } + } + } this.setStateFromProps(nextProps); } @@ -227,7 +258,7 @@ let QueryManager = class QueryManager extends React.Component { }; createOrUpdateDataQuery = () => { - const { appId, options, selectedDataSource, mode, queryName } = this.state; + const { appId, options, selectedDataSource, mode, queryName, shouldRunQuery } = this.state; const appVersionId = this.props.editingVersionId; const kind = selectedDataSource.kind; const dataSourceId = selectedDataSource.id === 'null' ? null : selectedDataSource.id; @@ -242,9 +273,13 @@ let QueryManager = class QueryManager extends React.Component { this.setState({ isUpdating: true }); dataqueryService .update(this.state.selectedQuery.id, queryName, options) - .then(() => { - toast.success('Query Updated'); - this.setState({ isUpdating: false, isFieldsChanged: false, restArrayValuesChanged: false }); + .then((data) => { + this.setState({ + isUpdating: shouldRunQuery ? true : false, + isFieldsChanged: false, + restArrayValuesChanged: false, + updatedQuery: shouldRunQuery ? { ...data, updateQuery: true } : {}, + }); this.props.dataQueriesChanged(); this.props.setStateOfUnsavedQueries(false); }) @@ -257,9 +292,14 @@ let QueryManager = class QueryManager extends React.Component { this.setState({ isCreating: true }); dataqueryService .create(appId, appVersionId, queryName, kind, options, dataSourceId) - .then(() => { + .then((data) => { toast.success('Query Added'); - this.setState({ isCreating: false, isFieldsChanged: false, restArrayValuesChanged: false }); + this.setState({ + isCreating: shouldRunQuery ? true : false, + isFieldsChanged: false, + restArrayValuesChanged: false, + updatedQuery: shouldRunQuery ? { ...data, updateQuery: false } : {}, + }); this.props.dataQueriesChanged(); this.props.setStateOfUnsavedQueries(false); }) @@ -329,6 +369,17 @@ let QueryManager = class QueryManager extends React.Component { this.optionchanged('events', events); }; + updateButtonText = (text, shouldRunQuery) => { + if (this.state.mode === 'edit') { + this.buttonConfig = { ...this.buttonConfig, editMode: { text: text, shouldRunQuery: shouldRunQuery } }; + localStorage.setItem('queryManagerButtonConfig', JSON.stringify(this.buttonConfig)); + } else { + this.buttonConfig = { ...this.buttonConfig, createMode: { text: text, shouldRunQuery: shouldRunQuery } }; + localStorage.setItem('queryManagerButtonConfig', JSON.stringify(this.buttonConfig)); + } + this.setState({ buttonText: text, shouldRunQuery: shouldRunQuery }); + }; + render() { const { dataSources, @@ -355,7 +406,7 @@ let QueryManager = class QueryManager extends React.Component { ElementToRender = allSources[sourcecomponentName]; } - let buttonText = mode === 'edit' ? 'Save' : 'Create'; + let dropDownButtonText = mode === 'edit' ? 'Save' : 'Create'; const buttonDisabled = isUpdating || isCreating; const mockDataQueryComponent = this.mockDataQueryAsComponent(); const Icon = allSvgs[this?.state?.selectedDataSource?.kind]; @@ -438,16 +489,39 @@ let QueryManager = class QueryManager extends React.Component { )} {selectedDataSource && (addingQuery || editingQuery) && ( - + + + + + { + this.updateButtonText(dropDownButtonText, false); + }} + > + {dropDownButtonText} + + { + this.updateButtonText(`${dropDownButtonText} & Run`, true); + }} + > + {`${dropDownButtonText} & Run`} + + + )} @@ -523,6 +597,7 @@ let QueryManager = class QueryManager extends React.Component { changeDataSource={this.changeDataSource} handleBackButton={this.handleBackButton} darkMode={this.props.darkMode} + dataSourceModalHandler={this.props.dataSourceModalHandler} /> )}
diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index f806b65b8e..ff557774f0 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -786,7 +786,6 @@ export const widgets = [ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { - defaultValue: { type: 'code', displayName: 'Default value' }, text: { type: 'code', displayName: 'Text' }, loadingState: { type: 'toggle', displayName: 'Show loading state' }, }, @@ -1307,6 +1306,7 @@ export const widgets = [ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, properties: { + instructionText: { type: 'code', displayName: 'Instruction Text' }, enableDropzone: { type: 'code', displayName: 'Use Drop zone' }, enablePicker: { type: 'code', displayName: 'Use File Picker' }, enableMultiple: { type: 'code', displayName: 'Pick multiple files' }, @@ -1340,6 +1340,7 @@ export const widgets = [ showOnMobile: { value: '{{false}}' }, }, properties: { + instructionText: { value: 'Drag and Drop some files here, or click to select files' }, enableDropzone: { value: '{{true}}' }, enablePicker: { value: '{{true}}' }, maxFileCount: { value: '{{2}}' }, @@ -1695,6 +1696,7 @@ export const widgets = [ backgroundColor: { type: 'color' }, visibility: { type: 'toggle', displayName: 'Visibility' }, disabledState: { type: 'toggle', displayName: 'Disable' }, + borderRadius: { type: 'number', displayName: 'Border radius' }, }, exposedVariables: { data: [{}], @@ -1719,6 +1721,7 @@ export const widgets = [ backgroundColor: { value: '#fff' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + borderRadius: { value: '{{0}}' }, }, }, }, @@ -2111,6 +2114,44 @@ export const widgets = [ }, }, }, + { + name: 'Html', + displayName: 'HTML Viewer', + description: 'HTML Viewer', + component: 'Html', + defaultSize: { + width: 10, + height: 310, + }, + properties: { + rawHtml: { type: 'code', displayName: 'Raw HTML' }, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + events: {}, + styles: { + visibility: { type: 'toggle', displayName: 'Visibility' }, + }, + exposedVariables: {}, + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + rawHtml: { + value: `
You can build your custom HTML-CSS template here
`, + }, + }, + events: [], + styles: { + visibility: { value: '{{true}}' }, + }, + }, + }, { name: 'VerticalDivider', displayName: 'Vertical Divider', @@ -2384,4 +2425,70 @@ ReactDOM.render(, document.body);`, }, }, }, + { + name: 'KanbanBoard', + displayName: 'Kanban Board', + description: 'Kanban Board', + component: 'KanbanBoard', + defaultSize: { + width: 40, + height: 490, + }, + others: { + showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, + showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, + }, + properties: { + columns: { type: 'code', displayName: 'Columns' }, + cardData: { type: 'code', displayName: 'Card Data' }, + enableAddCard: { type: 'toggle', displayName: 'Enable Add Card' }, + }, + events: { + onCardAdded: { displayName: 'Card added' }, + onCardRemoved: { displayName: 'Card removed' }, + onCardMoved: { displayName: 'Card moved' }, + onCardSelected: { displayName: 'Card selected' }, + onCardUpdated: { displayName: 'Card updated' }, + }, + styles: { + disabledState: { type: 'toggle', displayName: 'Disable' }, + visibility: { type: 'toggle', displayName: 'Visibility' }, + width: { type: 'number', displayName: 'Width' }, + minWidth: { type: 'number', displayName: 'Min Width' }, + accentColor: { type: 'color', displayName: 'Accent color' }, + }, + exposedVariables: { + columns: {}, + lastAddedCard: {}, + lastRemovedCard: {}, + lastCardMovement: {}, + lastUpdatedCard: {}, + }, + definition: { + others: { + showOnDesktop: { value: '{{true}}' }, + showOnMobile: { value: '{{false}}' }, + }, + properties: { + columns: { + value: '{{[{ "id": "1", "title": "to do" },{ "id": "2", "title": "in progress" }]}}', + }, + cardData: { + value: + '{{[{ id: "01", title: "one", columnId: "1" },{ id: "02", title: "two", columnId: "1" },{ id: "03", title: "three", columnId: "2" }]}}', + }, + enableAddCard: { + value: `{{true}}`, + }, + }, + events: [], + styles: { + visibility: { value: '{{true}}' }, + disabledState: { value: '{{false}}' }, + width: { value: '{{400}}' }, + minWidth: { value: '{{200}}' }, + textColor: { value: '#4d72fa' }, + }, + }, + }, ]; diff --git a/frontend/src/HomePage/FolderMenu.jsx b/frontend/src/HomePage/FolderMenu.jsx new file mode 100644 index 0000000000..5d7d02ec0e --- /dev/null +++ b/frontend/src/HomePage/FolderMenu.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import Popover from 'react-bootstrap/Popover'; + +export const FolderMenu = function FolderMenu({ + deleteFolder, + editFolder, + canDeleteFolder, + canUpdateFolder, + onMenuOpen, + darkMode, +}) { + const closeMenu = () => { + document.body.click(); + }; + const Field = ({ text, onClick, customClass }) => { + return ( +
+ { + closeMenu(); + onClick(); + }} + > + {text} + +
+ ); + }; + + return ( + + +
+ {canUpdateFolder && } + {canDeleteFolder && } +
+
+ + } + > +
+ +
+
+ ); +}; diff --git a/frontend/src/HomePage/Folders.jsx b/frontend/src/HomePage/Folders.jsx index b10683767a..3a37488ebe 100644 --- a/frontend/src/HomePage/Folders.jsx +++ b/frontend/src/HomePage/Folders.jsx @@ -1,7 +1,10 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { folderService } from '@/_services'; import { toast } from 'react-hot-toast'; import Modal from './Modal'; +import { FolderMenu } from './FolderMenu'; +import useHover from '@/_hooks/useHover'; +import { ConfirmDialog } from '@/_components'; export const Folders = function Folders({ folders, @@ -10,9 +13,27 @@ export const Folders = function Folders({ folderChanged, foldersChanged, canCreateFolder, + canUpdateFolder, + canDeleteFolder, darkMode, }) { const [isLoading, setLoadingStatus] = useState(foldersLoading); + const [isMenuOpen, setMenuOpen] = useState(false); + const [hoverRef, isHovered] = useHover(); + const [focused, setFocused] = useState(false); + + const onMenuToggle = useCallback( + (status) => { + setMenuOpen(!!status); + !status && !isHovered && setFocused(false); + }, + [isHovered] + ); + + useEffect(() => { + !isMenuOpen && setFocused(!!isHovered); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isHovered]); useEffect(() => { setLoadingStatus(foldersLoading); @@ -20,26 +41,28 @@ export const Folders = function Folders({ const [showForm, setShowForm] = useState(false); const [isCreating, setCreationStatus] = useState(false); + const [isDeleting, setDeletionStatus] = useState(false); + const [isUpdating, setUpdationStatus] = useState(false); + const [deletingFolder, setDeletingFolder] = useState(null); + const [updatingFolder, setUpdatingFolder] = useState(null); const [newFolderName, setNewFolderName] = useState(''); + const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); + const [showUpdateForm, setShowUpdateForm] = useState(false); const [activeFolder, setActiveFolder] = useState(currentFolder || {}); function saveFolder() { - if (!newFolderName || !newFolderName.trim()) { - toast.error("folder name can't be empty.", { - position: 'top-center', + if (validateName()) { + setCreationStatus(true); + folderService.create(newFolderName).then(() => { + toast.success('Folder created.', { + position: 'top-center', + }); + setCreationStatus(false); + setShowForm(false); + setNewFolderName(''); + foldersChanged(); }); - return; } - setCreationStatus(true); - folderService.create(newFolderName).then(() => { - toast.success('folder created.', { - position: 'top-center', - }); - setCreationStatus(false); - setShowForm(false); - setNewFolderName(''); - foldersChanged(); - }); } function handleFolderChange(folder) { @@ -47,8 +70,85 @@ export const Folders = function Folders({ folderChanged(folder); } + function deleteFolder(folder) { + setShowDeleteConfirmation(true); + setDeletingFolder(folder); + } + + function updateFolder(folder) { + setNewFolderName(folder.name); + setShowUpdateForm(true); + setUpdatingFolder(folder); + } + + function executeDeletion() { + setDeletionStatus(true); + folderService + .deleteFolder(deletingFolder.id) + .then(() => { + toast.success('Folder has been deleted.', { + position: 'top-center', + }); + setShowDeleteConfirmation(false); + setDeletionStatus(false); + foldersChanged(); + }) + .catch(({ error }) => { + toast.error(error); + setShowDeleteConfirmation(false); + setDeletionStatus(false); + }); + } + + function cancelDeleteDialog() { + setShowDeleteConfirmation(false); + setDeletingFolder(null); + } + + function validateName() { + if (!newFolderName?.trim()) { + toast.error("Folder name can't be empty.", { + position: 'top-center', + }); + return false; + } + return true; + } + + function executeEditFolder() { + if (validateName()) { + setUpdationStatus(true); + folderService + .updateFolder(newFolderName, updatingFolder.id) + .then(() => { + toast.success('Folder has been updated.', { + position: 'top-center', + }); + setUpdationStatus(false); + setShowUpdateForm(false); + setNewFolderName(''); + foldersChanged(); + }) + .catch(({ error }) => { + toast.error(error); + setNewFolderName(''); + setUpdationStatus(false); + }); + } + } + return (
+ executeDeletion()} + onCancel={() => cancelDeleteDialog()} + darkMode={darkMode} + /> +
Folders
{canCreateFolder && ( -
setShowForm(true)}> +
{ + setNewFolderName(''); + setShowForm(true); + }} + > + Create new folder
)} @@ -88,22 +194,40 @@ export const Folders = function Folders({ ? folders.map((folder, index) => ( handleFolderChange(folder)} + } ${darkMode && 'dark'} ${focused ? ' highlight' : ''}`} > - - - - {`${folder.name}${folder.count > 0 ? ` (${folder.count})` : ''}`} +
handleFolderChange(folder)} className="flex-grow-1"> + + + + {`${folder.name}${folder.count > 0 ? ` (${folder.count})` : ''}`} +
+
+ {(canDeleteFolder || canUpdateFolder) && ( + deleteFolder(folder)} + editFolder={() => updateFolder(folder)} + darkMode={darkMode} + /> + )} +
)) : !isLoading && (
You haven't created any folders. Use folders to organize your apps
)} - setShowForm(false)} title="Create folder"> + (showUpdateForm ? setShowUpdateForm(false) : setShowForm(false))} + title={showUpdateForm ? 'Update Folder' : 'Create folder'} + >
setNewFolderName(e.target.value)} className="form-control" placeholder="folder name" - disabled={isCreating} + disabled={isCreating || isUpdating} + value={newFolderName} maxLength={25} />
- -
diff --git a/frontend/src/HomePage/Header.jsx b/frontend/src/HomePage/Header.jsx index 74c57f76ff..3b3dc9e809 100644 --- a/frontend/src/HomePage/Header.jsx +++ b/frontend/src/HomePage/Header.jsx @@ -27,6 +27,7 @@ export default function Header({
diff --git a/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx b/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx index a9587b792a..36f96313a7 100644 --- a/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx +++ b/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx @@ -280,6 +280,10 @@ class ManageGroupPermissionResources extends React.Component { selectedUserIds, } = this.state; + const folder_permission = groupPermission + ? groupPermission.folder_create && groupPermission.folder_delete && groupPermission.folder_update + : false; + const appSelectOptions = appsNotInGroup.map((app) => { return { name: app.name, value: app.id }; }); @@ -306,10 +310,14 @@ class ManageGroupPermissionResources extends React.Component { ) : (
  1. - User groups + + User groups +
  2. - {this.humanizeIfDefaultGroupName(groupPermission.group)} + + {this.humanizeIfDefaultGroupName(groupPermission.group)} +
)} @@ -325,18 +333,21 @@ class ManageGroupPermissionResources extends React.Component { this.setState({ currentTab: 'apps' })} className={cx('nav-item nav-link', { active: currentTab === 'apps' })} + data-cy="apps-link" > Apps this.setState({ currentTab: 'users' })} className={cx('nav-item nav-link', { active: currentTab === 'users' })} + data-cy="users-link" > Users this.setState({ currentTab: 'permissions' })} className={cx('nav-item nav-link', { active: currentTab === 'permissions' })} + data-cy="permissions-link" > Permissions @@ -377,8 +388,8 @@ class ManageGroupPermissionResources extends React.Component { - - + + @@ -436,6 +447,7 @@ class ManageGroupPermissionResources extends React.Component { onClick={() => { this.removeAppFromGroup(groupPermission.id, app.id); }} + data-cy="delete-link" > Delete @@ -484,8 +496,8 @@ class ManageGroupPermissionResources extends React.Component {
NamePermissionsNamePermissions
- - + + @@ -537,8 +549,8 @@ class ManageGroupPermissionResources extends React.Component {
NameEmailNameEmail
- - + + @@ -560,7 +572,7 @@ class ManageGroupPermissionResources extends React.Component { ) : ( <> - + @@ -597,7 +615,7 @@ class ManageGroupPermissionResources extends React.Component { - + diff --git a/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx b/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx index 16a0bd5500..8b297c79cb 100644 --- a/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx +++ b/frontend/src/ManageGroupPermissions/ManageGroupPermissions.jsx @@ -132,11 +132,17 @@ class ManageGroupPermissions extends React.Component {
-

User Groups

+

+ User Groups +

{!showNewGroupForm && ( -
this.setState({ showNewGroupForm: true })}> +
this.setState({ showNewGroupForm: true })} + data-cy="create-new-group-button" + > Create new group
)} @@ -150,7 +156,9 @@ class ManageGroupPermissions extends React.Component {
-

Add new group

+

+ Add new group +

{ this.changeNewGroupName(e.target.value); }} + data-cy="group-name-input" />
@@ -185,6 +194,7 @@ class ManageGroupPermissions extends React.Component { }) } disabled={creatingGroup} + data-cy="cancel-button" > Cancel @@ -192,6 +202,7 @@ class ManageGroupPermissions extends React.Component { type="submit" className={`btn mx-2 btn-primary ${creatingGroup ? 'btn-loading' : ''}`} disabled={creatingGroup} + data-cy="create-group-button" > Create Group @@ -208,7 +219,7 @@ class ManageGroupPermissions extends React.Component {
ResourcePermissionsResourcePermissions
AppsApps
FoldersFolders
- + @@ -236,13 +247,15 @@ class ManageGroupPermissions extends React.Component { {groups.map((permissionGroup) => ( diff --git a/frontend/src/ManageSSO/Form.jsx b/frontend/src/ManageSSO/Form.jsx index d61c078995..cd769a0e69 100644 --- a/frontend/src/ManageSSO/Form.jsx +++ b/frontend/src/ManageSSO/Form.jsx @@ -11,9 +11,7 @@ export function Form({ settings, updateData }) { const enabled_tmp = !enabled; setEnabled(enabled_tmp); updateData('form', { id: data.id, enabled: enabled_tmp }); - toast.success(`${enabled_tmp ? 'Enabled' : 'Disabled'} Form login`, { - position: 'top-center', - }); + toast.success(`${enabled_tmp ? 'Enabled' : 'Disabled'} Password login`, { position: 'top-center' }); }, () => { toast.error('Error saving sso configurations', { @@ -27,13 +25,21 @@ export function Form({ settings, updateData }) {
-
+
Password Login - {enabled ? 'Enabled' : 'Disabled'} + + {enabled ? 'Enabled' : 'Disabled'} +
diff --git a/frontend/src/ManageSSO/GeneralSettings.jsx b/frontend/src/ManageSSO/GeneralSettings.jsx index f1b74dfd42..1f7a970847 100644 --- a/frontend/src/ManageSSO/GeneralSettings.jsx +++ b/frontend/src/ManageSSO/GeneralSettings.jsx @@ -35,7 +35,9 @@ export function GeneralSettings({ settings, updateData }) { return (
-
General Settings
+
+ General Settings +
@@ -46,15 +48,22 @@ export function GeneralSettings({ settings, updateData }) { type="checkbox" onChange={() => setEnableSignUp((enableSignUp) => !enableSignUp)} checked={enableSignUp} + data-cy="form-check-input" /> - Enable signup + + Enable signup +
-
New account will be created for user's first time SSO sign in
+
+ New account will be created for user's first time SSO sign in +
- +
setDomain(e.target.value)} + data-cy="allowed-domain-input" />
{!isSingleOrganization && (
- +
{`${window.location.protocol}//${window.location.host}/login/${authenticationService?.currentUserValue?.organization_id}`}
-
Use this URL to login directly to this workspace
+
Use this URL to login directly to this workspace
)}
- diff --git a/frontend/src/ManageSSO/Git.jsx b/frontend/src/ManageSSO/Git.jsx index 6759e5aadd..d609c2b4ef 100644 --- a/frontend/src/ManageSSO/Git.jsx +++ b/frontend/src/ManageSSO/Git.jsx @@ -60,13 +60,21 @@ export function Git({ settings, updateData }) {
-
+
GitHub - {enabled ? 'Enabled' : 'Disabled'} + + {enabled ? 'Enabled' : 'Disabled'} +
@@ -74,7 +82,9 @@ export function Git({ settings, updateData }) {
- +
setClientId(e.target.value)} + data-cy="client-id-input" />
-
{configId && (
- -
{`${window.location.protocol}//${window.location.host}/sso/git/${configId}`}
+ +
{`${window.location.protocol}//${window.location.host}/sso/git/${configId}`}
)}
- diff --git a/frontend/src/ManageSSO/Google.jsx b/frontend/src/ManageSSO/Google.jsx index 025b3b3725..a884f153ec 100644 --- a/frontend/src/ManageSSO/Google.jsx +++ b/frontend/src/ManageSSO/Google.jsx @@ -58,13 +58,21 @@ export function Google({ settings, updateData }) {
-
+
Google - {enabled ? 'Enabled' : 'Disabled'} + + {enabled ? 'Enabled' : 'Disabled'} +
@@ -72,7 +80,9 @@ export function Google({ settings, updateData }) {
- +
setClientId(e.target.value)} + data-cy="client-id-input" />
{configId && (
- -
{`${window.location.protocol}//${window.location.host}/sso/google/${configId}`}
+ +
{`${window.location.protocol}//${window.location.host}/sso/google/${configId}`}
)}
- diff --git a/frontend/src/ManageSSO/ManageSSO.jsx b/frontend/src/ManageSSO/ManageSSO.jsx index d8f6f28228..279b3b02e7 100644 --- a/frontend/src/ManageSSO/ManageSSO.jsx +++ b/frontend/src/ManageSSO/ManageSSO.jsx @@ -89,7 +89,9 @@ export function ManageSSO({ switchDarkMode, darkMode }) {
-

Manage SSO

+

+ Manage SSO +

diff --git a/frontend/src/RedirectSso/RedirectSso.jsx b/frontend/src/RedirectSso/RedirectSso.jsx index abce8574ca..4e123d00e4 100644 --- a/frontend/src/RedirectSso/RedirectSso.jsx +++ b/frontend/src/RedirectSso/RedirectSso.jsx @@ -81,7 +81,12 @@ export const RedirectSso = function RedirectSso() {

Please login with password and you can setup sso using workspace - Manage SSO menu. + + Manage SSO menu. +

@@ -111,7 +116,10 @@ export const RedirectSso = function RedirectSso() { ) : (

Please login with password and you can setup sso using workspace - + Manage SSO menu.

diff --git a/frontend/src/_components/ConfirmDialog.jsx b/frontend/src/_components/ConfirmDialog.jsx index 181cdaf903..0941a8328d 100644 --- a/frontend/src/_components/ConfirmDialog.jsx +++ b/frontend/src/_components/ConfirmDialog.jsx @@ -28,9 +28,9 @@ export function ConfirmDialog({ show, message, onConfirm, onCancel, confirmButto contentClassName={darkMode ? 'theme-dark' : ''} >
- {message} + {message} -
diff --git a/frontend/src/_components/Menu.jsx b/frontend/src/_components/Menu.jsx index 6f4dde36b6..c6f0cd7efd 100644 --- a/frontend/src/_components/Menu.jsx +++ b/frontend/src/_components/Menu.jsx @@ -3,7 +3,7 @@ import React from 'react'; export function Menu({ onChange, items, selected }) { return (
-
    +
      {items && items.map((item) => (
    • onChange(item.id)} className={selected === item.id ? 'active' : ''}> diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 09a9d13fcb..6b9bbf0e84 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -167,7 +167,7 @@ function logoutAction(_ref) { return Promise.resolve(); } -function executeAction(_ref, event, mode, customVariables) { +export const executeAction = (_ref, event, mode, customVariables) => { console.log('nopski', customVariables); if (event) { switch (event.actionId) { @@ -315,7 +315,7 @@ function executeAction(_ref, event, mode, customVariables) { } } } -} +}; export async function onEvent(_ref, eventName, options, mode = 'edit') { let _self = _ref; @@ -493,6 +493,11 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') { 'onCalendarViewChange', 'onSearchTextChanged', 'onPageChange', + 'onCardAdded', + 'onCardRemoved', + 'onCardMoved', + 'onCardSelected', + 'onCardUpdated', 'onTabSwitch', ].includes(eventName) ) { @@ -543,7 +548,7 @@ export function getQueryVariables(options, state) { return queryVariables; } -export function previewQuery(_ref, query) { +export function previewQuery(_ref, query, calledFromQuery = false) { const options = getQueryVariables(query.options, _ref.props.currentState); _ref.setState({ previewLoading: true }); @@ -551,7 +556,7 @@ export function previewQuery(_ref, query) { return new Promise(function (resolve, reject) { let queryExecutionPromise = null; if (query.kind === 'runjs') { - queryExecutionPromise = executeMultilineJS(_ref.state.currentState, query.options.code); + queryExecutionPromise = executeMultilineJS(_ref, query.options.code, true); } else { queryExecutionPromise = dataqueryService.preview(query, options); } @@ -564,7 +569,11 @@ export function previewQuery(_ref, query) { finalData = runTransformation(_ref, finalData, query.options.transformation, query); } - _ref.setState({ previewLoading: false, queryPreviewData: finalData }); + if (calledFromQuery) { + _ref.setState({ previewLoading: false }); + } else { + _ref.setState({ previewLoading: false, queryPreviewData: finalData }); + } switch (data.status) { case 'failed': { toast.error(`${data.message}: ${data.description}`); @@ -583,7 +592,7 @@ export function previewQuery(_ref, query) { } } - resolve(); + resolve({ status: data.status, data: finalData }); }) .catch(({ error, data }) => { _ref.setState({ previewLoading: false, queryPreviewData: data }); @@ -640,7 +649,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode) let queryExecutionPromise = null; if (query.kind === 'runjs') { console.log('here'); - queryExecutionPromise = executeMultilineJS(_self.state.currentState, query.options.code); + queryExecutionPromise = executeMultilineJS(_self, query.options.code, false, confirmed, mode); } else { queryExecutionPromise = dataqueryService.run(queryId, options); } @@ -686,7 +695,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode) }, }, () => { - resolve(); + resolve(data); onEvent(_self, 'onDataQueryFailure', { definition: { events: dataQuery.options.events } }); } ); @@ -720,7 +729,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode) }, }, () => { - resolve(); + resolve(finalData); onEvent(_self, 'onDataQueryFailure', { definition: { events: dataQuery.options.events } }); } ); @@ -759,7 +768,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode) }, }, () => { - resolve(); + resolve({ status: 'ok', data: finalData }); onEvent(_self, 'onDataQuerySuccess', { definition: { events: dataQuery.options.events } }, mode); } ); @@ -779,7 +788,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode) }, }, () => { - resolve(); + resolve({ status: 'failed', message: error }); } ); }); diff --git a/frontend/src/_helpers/utils.js b/frontend/src/_helpers/utils.js index 2067dd9397..9f41fa517f 100644 --- a/frontend/src/_helpers/utils.js +++ b/frontend/src/_helpers/utils.js @@ -3,6 +3,7 @@ import moment from 'moment'; import _ from 'lodash'; import axios from 'axios'; import JSON5 from 'json5'; +import { previewQuery, executeAction } from '@/_helpers/appUtils'; export function findProp(obj, prop, defval) { if (typeof defval === 'undefined') defval = null; @@ -269,13 +270,67 @@ export function validateEmail(email) { return emailRegex.test(email); } -export async function executeMultilineJS(currentState, code) { +export async function executeMultilineJS(_ref, code, isPreview, confirmed = undefined, mode = '') { + const { currentState } = _ref.state; let result = {}, error = null; + const actions = { + runQuery: function (queryName = '') { + const query = _ref.state.dataQueries.find((query) => query.name === queryName); + if (_.isEmpty(query)) return; + if (isPreview) { + return previewQuery(_ref, query, true); + } else { + const event = { + actionId: 'run-query', + queryId: query.id, + queryName: query.name, + }; + return executeAction(_ref, event, mode, {}); + } + }, + setVariable: function (key = '', value = '') { + if (key) { + const event = { + actionId: 'set-custom-variable', + key, + value, + }; + return executeAction(_ref, event, mode, {}); + } + }, + unSetVariable: function (key = '') { + if (key) { + const event = { + actionId: 'unset-custom-variable', + key, + }; + return executeAction(_ref, event, mode, {}); + } + }, + }; + + for (const key of Object.keys(currentState.queries)) { + currentState.queries[key] = { + ...currentState.queries[key], + run: () => actions.runQuery(key), + }; + } + try { const AsyncFunction = new Function(`return Object.getPrototypeOf(async function(){}).constructor`)(); - var evalFn = new AsyncFunction('moment', '_', 'components', 'queries', 'globals', 'axios', 'variables', code); + var evalFn = new AsyncFunction( + 'moment', + '_', + 'components', + 'queries', + 'globals', + 'axios', + 'variables', + 'actions', + code + ); result = { status: 'ok', data: await evalFn( @@ -285,7 +340,8 @@ export async function executeMultilineJS(currentState, code) { currentState.queries, currentState.globals, axios, - currentState.variables + currentState.variables, + actions ), }; } catch (err) { diff --git a/frontend/src/_services/folder.service.js b/frontend/src/_services/folder.service.js index 637ede3480..0d2e177e85 100644 --- a/frontend/src/_services/folder.service.js +++ b/frontend/src/_services/folder.service.js @@ -3,9 +3,11 @@ import { authHeader, handleResponse } from '@/_helpers'; export const folderService = { create, + deleteFolder, getAll, addToFolder, removeAppFromFolder, + updateFolder, }; function getAll(searchKey = '') { @@ -26,6 +28,27 @@ function create(name) { return fetch(`${config.apiUrl}/folders`, requestOptions).then(handleResponse); } +function updateFolder(name, id) { + const body = { + name, + }; + + const requestOptions = { + method: 'PUT', + headers: authHeader(), + body: JSON.stringify(body), + }; + return fetch(`${config.apiUrl}/folders/${id}`, requestOptions).then(handleResponse); +} + +function deleteFolder(id) { + const requestOptions = { + method: 'DELETE', + headers: authHeader(), + }; + return fetch(`${config.apiUrl}/folders/${id}`, requestOptions).then(handleResponse); +} + function addToFolder(appId, folderId) { const body = { app_id: appId, diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 1d6cfbb185..ee55ce1969 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -537,6 +537,14 @@ button { border: solid rgba(0, 0, 0, 0.125); border-width: 1px 0px 0px 0px; + .create-save-button-dropdown-toggle { + background-color: #4a6ce8; + } + + .create-save-button-dropdown-toggle:hover { + background-color: #4066f0; + } + .table-responsive { scrollbar-width: none; } @@ -1696,7 +1704,6 @@ button { td { min-height: 40px; overflow-x: initial; - margin: auto; .text-container { padding: 0; @@ -4165,6 +4172,25 @@ input[type="text"] { color: #0565ff; cursor: pointer; } + + .menu-ico { + cursor: pointer; + padding: 3px; + border-radius: 13px; + &__open { + background-color: #d2ddec; + } + img { + padding: 0px; + height: 14px; + width: 14px; + vertical-align: unset; + } + } + + .menu-ico:hover { + background-color: #d2ddec; + } } /** @@ -5130,6 +5156,8 @@ div#driver-page-overlay { .node-key { font-weight: 400 !important; margin-left: -0.25rem !important; + justify-content: start !important; + min-width: fit-content !important; } .node-key-outline { @@ -5320,6 +5348,120 @@ div#driver-page-overlay { } } } + + +//Kanban board + +.kanban-container.dark-themed { + background-color: $bg-dark-light !important; + .kanban-column { + .card-header { + background-color: #324156 !important; + } + } +} + +.kanban-container { + background-color: #fefefe; + + .kanban-column { + background-color: #f4f4f4; + padding: 0 !important; + height: fit-content !important; + + .card-body { + &:hover { + overflow-y: auto !important; + &::-webkit-scrollbar { + width: 0 !important; + height: 0 !important; + } + } + } + + .card-header { + background-color: #fefefe; + + .badge { + font-size: 12px !important; + } + } + + .card-body .dnd-card { + border-radius: 5px !important; + } + .dnd-card.card { + height: 52px !important; + padding: 5px !important; + } + .dnd-card.card.card-dark { + background-color: $bg-dark !important; + } + } + + .kanban-board-add-group { + justify-content: center; + align-items: center; + cursor: pointer; + color: rgba(0, 0, 0, 0.5); + background-color: transparent; + border-style: dashed; + border-color: rgba(0, 0, 0, 0.08); + display: flex; + flex-direction: column; + grid-auto-rows: max-content; + overflow: hidden; + box-sizing: border-box; + appearance: none; + outline: none; + margin: 10px; + border-radius: 5px; + min-width: 350px; + height: 200px; + font-size: 1em; + } + .add-card-btn { + font-size: 1em; + font-weight: 400; + color: #3e525b; + border-radius: 5px; + padding: 5px; + margin: 5px; + background-color: transparent; + border-style: dashed; + border-color: rgba(0, 0, 0, 0.08); + cursor: pointer; + transition: all 0.2s ease-in-out; + &:hover { + background-color: #e6e6e6; + } + } +} + +.cursor-pointer { + cursor: pointer; +} +.cursor-text { + cursor: text; +} + +.bade-component { + display: inline-flex; + justify-content: center; + align-items: center; + overflow: hidden; + user-select: none; + padding: calc(0.25rem - 1px) 0.25rem; + height: 1.25rem; + border: 1px solid transparent; + min-width: 1.25rem; + font-weight: 600; + font-size: .625rem; + letter-spacing: .04em; + text-transform: uppercase; + vertical-align: bottom; + border-radius: 4px; +} // sso-helper-page .sso-helper-container{ width: 60vw; diff --git a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx index 85bdfcb89e..155e838b9a 100644 --- a/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx +++ b/frontend/src/_ui/JSONTreeViewer/JSONNode.jsx @@ -41,7 +41,6 @@ export const JSONNode = ({ data, ...restProps }) => { const [showHiddenOptionsForNode, setShowHiddenOptionsForNode] = React.useState(false); const [showHiddenOptionButtons, setShowHiddenOptionButtons] = React.useState([]); - const [onSelectDispatchActions, setOnSelectDispatchActions] = React.useState([]); React.useEffect(() => { if (showHiddenOptionButtons) { @@ -50,21 +49,6 @@ export const JSONNode = ({ data, ...restProps }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - React.useEffect(() => { - if (useActions && currentNode) { - const actions = getOnSelectLabelDispatchActions(currentNode, path); - const onSelectDispatchActions = - Object.prototype.toString.call(actions).slice(8, -1) === 'array' - ? actions.filter((action) => action.onSelect) - : []; - if (onSelectDispatchActions.length > 0) { - setOnSelectDispatchActions(onSelectDispatchActions); - } - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedNode]); - const toggleExpandNode = (node) => { if (expandable) { updateSelectedNode(null); @@ -76,7 +60,7 @@ export const JSONNode = ({ data, ...restProps }) => { }; const onSelect = (data, currentNode, path) => { - const actions = onSelectDispatchActions; + const actions = getOnSelectLabelDispatchActions(currentNode, path)?.filter((action) => action.onSelect); actions.forEach((action) => action.dispatchAction(data, currentNode)); if (!expandWithLabels) { @@ -309,6 +293,7 @@ export const JSONNode = ({ data, ...restProps }) => { className={cx('d-flex row-flex mt-1 font-monospace container-fluid px-1', { 'json-node-element': !expandable, })} + onMouseLeave={() => updateHoveredNode(null)} >
      {
      updateHoveredNode(currentNode, currentNodePath)} - onMouseLeave={() => updateHoveredNode(null)} >
      updateHoveredNode(currentNode, currentNodePath)} > {$NODEIcon &&
      {$NODEIcon}
      } {$key} {$NODEType} diff --git a/package-lock.json b/package-lock.json index d7205cbd31..3683b6149d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,12 @@ { - "name": "ToolJet", + "name": "tooljet", + "version": "1.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { + "name": "tooljet", + "version": "1.18.0", "dependencies": { "@nestjs/cli": "^8.1.0", "@nestjs/mapped-types": "*", @@ -17,6 +20,7 @@ "@babel/preset-env": "^7.4.3", "@babel/preset-react": "^7.0.0", "@cypress/webpack-preprocessor": "^5.11.1", + "@tooljet/cli": "^0.0.12", "babel-loader": "^8.0.5", "eslint": "^7.25.0", "faker": "^5.5.3", @@ -782,6 +786,28 @@ "node": ">=8" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true, + "peer": true + }, "node_modules/@nestjs/cli": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.1.0.tgz", @@ -1164,6 +1190,253 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oclif/color": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@oclif/color/-/color-1.0.1.tgz", + "integrity": "sha512-qjYr+izgWdIVOroiBKqTzQgc1r5Wd9QB1J7yGM2EeelqhBARiiVLRZL45vhV4zdyTRdDkZS0EBzFwQap+nliLA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.2.1", + "chalk": "^4.1.0", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "tslib": "^2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@oclif/color/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@oclif/core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-1.9.3.tgz", + "integrity": "sha512-npxWULRu+iW9AuUNoCH118MNI8cxYIkcWkknz3mCDumTo11FC+h3OY1cMtlclqZHfZcDHh4iaSkNMX/7se9GUQ==", + "dev": true, + "dependencies": { + "@oclif/linewrap": "^1.0.0", + "@oclif/screen": "^3.0.2", + "ansi-escapes": "^4.3.2", + "ansi-styles": "^4.3.0", + "cardinal": "^2.1.1", + "chalk": "^4.1.2", + "clean-stack": "^3.0.1", + "cli-progress": "^3.10.0", + "debug": "^4.3.4", + "ejs": "^3.1.6", + "fs-extra": "^9.1.0", + "get-package-type": "^0.1.0", + "globby": "^11.1.0", + "hyperlinker": "^1.0.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "js-yaml": "^3.14.1", + "natural-orderby": "^2.0.3", + "object-treeify": "^1.1.33", + "password-prompt": "^1.1.2", + "semver": "^7.3.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "supports-hyperlinks": "^2.2.0", + "tslib": "^2.3.1", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@oclif/core/node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@oclif/core/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@oclif/core/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@oclif/core/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@oclif/linewrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@oclif/linewrap/-/linewrap-1.0.0.tgz", + "integrity": "sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw==", + "dev": true + }, + "node_modules/@oclif/plugin-help": { + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-5.1.12.tgz", + "integrity": "sha512-HvH/RubJxqCinP0vUWQLTOboT+SfjfL8h40s+PymkWaldIcXlpoRaJX50vz+SjZIs7uewZwEk8fzLqpF/BWXlg==", + "dev": true, + "dependencies": { + "@oclif/core": "^1.3.6" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@oclif/plugin-plugins": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-2.1.0.tgz", + "integrity": "sha512-Bgt+QpTlX7+Q0HkVgtbUGYQlo/hyzNBAaXH5l16ou9Ji5wfi5T+niV5AzQ14R7JF8ZDOTbUOU/NRBJ2bzLCaZQ==", + "dev": true, + "dependencies": { + "@oclif/color": "^1.0.1", + "@oclif/core": "^1.2.0", + "chalk": "^4.1.2", + "debug": "^4.1.0", + "fs-extra": "^9.0", + "http-call": "^5.2.2", + "load-json-file": "^5.3.0", + "npm-run-path": "^4.0.1", + "semver": "^7.3.2", + "tslib": "^2.0.0", + "yarn": "^1.22.17" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@oclif/plugin-plugins/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@oclif/screen": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.2.tgz", + "integrity": "sha512-S/SF/XYJeevwIgHFmVDAFRUvM3m+OjhvCAYMk78ZJQCYCQ5wS7j+LTt1ZEv2jpEEGg2tx/F6TYYWxddNAYHrFQ==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@tooljet/cli": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@tooljet/cli/-/cli-0.0.12.tgz", + "integrity": "sha512-vpLxiTiucpCItd2Fi2IPBeXNBBcqz/TzWPi0P53Uj1OPyQTZc5p4GAp32niJ5n3Hp/iu/j1viyxM4zaD+z/2qg==", + "dev": true, + "dependencies": { + "@oclif/core": "^1.6.0", + "@oclif/plugin-help": "^5.1.12", + "@oclif/plugin-plugins": "^2.1.0", + "@types/inquirer": "^8.1.3", + "hygen": "^6.2.0", + "inquirer": "^7.3.3", + "rimraf": "^3.0.2" + }, + "bin": { + "tooljet": "bin/run" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -1213,15 +1486,25 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", "dev": true }, + "node_modules/@types/inquirer": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.1.tgz", + "integrity": "sha512-wKW3SKIUMmltbykg4I5JzCVzUhkuD9trD6efAmYgN2MrSntY0SMRQzEnD3mkyJ/rv9NLbTC7g3hKKE86YwEDLw==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz", "integrity": "sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==" }, "node_modules/@types/node": { - "version": "16.4.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.4.tgz", - "integrity": "sha512-BH/jX0HjzElFCQdAwaEMwuGBQwm6ViDZ00X6LKdnRRmGWOzkWugEH4+7a0BwfHQ8DfPPCSd/mdsm3Nu8FKFu0w==" + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -1238,6 +1521,15 @@ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==" }, + "node_modules/@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -1437,9 +1729,9 @@ } }, "node_modules/acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -1539,9 +1831,9 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { "node": ">=8" } @@ -1560,6 +1852,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -1593,6 +1891,15 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1618,9 +1925,9 @@ } }, "node_modules/async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -1930,15 +2237,28 @@ "url": "https://opencollective.com/browserslist" } }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1950,6 +2270,85 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.1.0.tgz", + "integrity": "sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==", + "dev": true, + "dependencies": { + "camel-case": "^3.0.0", + "constant-case": "^2.0.0", + "dot-case": "^2.1.0", + "header-case": "^1.0.0", + "is-lower-case": "^1.1.0", + "is-upper-case": "^1.1.0", + "lower-case": "^1.1.1", + "lower-case-first": "^1.0.0", + "no-case": "^2.3.2", + "param-case": "^2.1.0", + "pascal-case": "^2.0.0", + "path-case": "^2.1.0", + "sentence-case": "^2.1.0", + "snake-case": "^2.1.0", + "swap-case": "^1.1.0", + "title-case": "^2.1.0", + "upper-case": "^1.1.1", + "upper-case-first": "^1.1.0" + } + }, + "node_modules/change-case/node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/change-case/node_modules/dot-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", + "integrity": "sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/change-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/change-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/change-case/node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/change-case/node_modules/pascal-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", + "integrity": "sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==", + "dev": true, + "dependencies": { + "camel-case": "^3.0.0", + "upper-case-first": "^1.1.0" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -2007,6 +2406,18 @@ "node": ">=8" } }, + "node_modules/cli-progress": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.11.1.tgz", + "integrity": "sha512-TTMA2LHrYaZeNMcgZGO10oYqj9hvd03pltNtVbu4ddeyDTHlYV7gWxsFiuvaQlgwMBFCv1TukcjiODWFlb16tQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cli-spinners": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", @@ -2185,6 +2596,25 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "node_modules/constant-case": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", + "integrity": "sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==", + "dev": true, + "dependencies": { + "snake-case": "^2.1.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -2428,9 +2858,9 @@ "integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==" }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { "ms": "2.1.2" }, @@ -2465,6 +2895,18 @@ "clone": "^1.0.2" } }, + "node_modules/degit": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/degit/-/degit-2.8.4.tgz", + "integrity": "sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng==", + "dev": true, + "bin": { + "degit": "degit" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2481,6 +2923,18 @@ "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2576,6 +3030,21 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ejs": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", + "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.3.749", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.749.tgz", @@ -2987,6 +3456,28 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3004,6 +3495,15 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -3038,6 +3538,36 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3261,6 +3791,15 @@ "node": ">= 0.12" } }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1" + } + }, "node_modules/fs-extra": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", @@ -3310,6 +3849,15 @@ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -3390,9 +3938,9 @@ } }, "node_modules/globals": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz", - "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==", + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3416,6 +3964,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -3470,6 +4047,31 @@ "he": "bin/he" } }, + "node_modules/header-case": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", + "integrity": "sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.3" + } + }, + "node_modules/header-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/header-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", @@ -3542,6 +4144,36 @@ "entities": "^2.0.0" } }, + "node_modules/http-call": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/http-call/-/http-call-5.3.0.tgz", + "integrity": "sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==", + "dev": true, + "dependencies": { + "content-type": "^1.0.4", + "debug": "^4.1.1", + "is-retry-allowed": "^1.1.0", + "is-stream": "^2.0.0", + "parse-json": "^4.0.0", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-call/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3579,6 +4211,327 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/hygen": { + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/hygen/-/hygen-6.2.7.tgz", + "integrity": "sha512-JKVS3cnOgKb+iY2/qP+6tq07P4U/gCmE2gmkSLDCoCGJONt2YCXimNT/2mzi2NuSzvisjjkVxwBY7kNvUufrAQ==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.19", + "chalk": "^4.1.2", + "change-case": "^3.1.0", + "debug": "^4.3.3", + "degit": "^2.8.4", + "ejs": "^3.1.6", + "enquirer": "^2.3.6", + "eslint-plugin-prettier": "^4.0.0", + "execa": "^5.0.0", + "front-matter": "^4.0.2", + "fs-extra": "^10.0.0", + "ignore-walk": "^4.0.1", + "inflection": "^1.12.0", + "ora": "^5.0.0", + "yargs-parser": "^21.0.0" + }, + "bin": { + "hygen": "dist/bin.js" + } + }, + "node_modules/hygen/node_modules/@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/hygen/node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/hygen/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/hygen/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hygen/node_modules/eslint": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.18.0.tgz", + "integrity": "sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/hygen/node_modules/eslint-plugin-prettier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/hygen/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/hygen/node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/hygen/node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/hygen/node_modules/espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/hygen/node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/hygen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/hygen/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/hygen/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hygen/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hygen/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hygen/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/hygen/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/hyperlinker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz", + "integrity": "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3618,6 +4571,18 @@ "node": ">= 4" } }, + "node_modules/ignore-walk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz", + "integrity": "sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3666,6 +4631,15 @@ "node": ">=8" } }, + "node_modules/inflection": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.2.tgz", + "integrity": "sha512-cmZlljCRTBFouT8UzMzrGcVEvkv6D/wBdcdKG7J1QH5cXjtU75Dm+P27v9EKu/Y43UYyCJd1WC4zLebRrC8NBw==", + "dev": true, + "engines": [ + "node >= 0.4.0" + ] + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3762,6 +4736,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3779,9 +4768,9 @@ } }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dependencies": { "is-extglob": "^2.1.1" }, @@ -3812,6 +4801,21 @@ "node": ">=8" } }, + "node_modules/is-lower-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.0" + } + }, + "node_modules/is-lower-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3858,6 +4862,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3885,6 +4898,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-upper-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==", + "dev": true, + "dependencies": { + "upper-case": "^1.1.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3913,6 +4947,24 @@ "node": ">=6" } }, + "node_modules/jake": { + "version": "10.8.5", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", + "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-worker": { "version": "27.0.6", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz", @@ -4231,6 +5283,53 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/load-json-file/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -4282,6 +5381,13 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "peer": true + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -4368,6 +5474,21 @@ "tslib": "^2.0.3" } }, + "node_modules/lower-case-first": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.2" + } + }, + "node_modules/lower-case-first/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -4419,6 +5540,15 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -4460,9 +5590,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4491,11 +5621,26 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/natural-orderby": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz", + "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -4558,6 +5703,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4759,6 +5913,92 @@ "tslib": "^2.0.3" } }, + "node_modules/password-prompt": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/password-prompt/-/password-prompt-1.1.2.tgz", + "integrity": "sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.1.0", + "cross-spawn": "^6.0.5" + } + }, + "node_modules/password-prompt/node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/password-prompt/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/password-prompt/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/password-prompt/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/password-prompt/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/password-prompt/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/password-prompt/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/path": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", @@ -4769,6 +6009,30 @@ "util": "^0.10.3" } }, + "node_modules/path-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz", + "integrity": "sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/path-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/path-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -4880,6 +6144,34 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -4958,6 +6250,26 @@ "node": ">=0.4.x" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ramda": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", @@ -4995,6 +6307,15 @@ "node": ">= 0.10" } }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "dependencies": { + "esprima": "~4.0.0" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -5002,9 +6323,9 @@ "peer": true }, "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "engines": { "node": ">=8" @@ -5125,6 +6446,16 @@ "node": ">=8" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5147,11 +6478,33 @@ "node": ">=0.12.0" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/rxjs": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -5198,9 +6551,9 @@ } }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5217,6 +6570,31 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "node_modules/sentence-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", + "integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case-first": "^1.1.2" + } + }, + "node_modules/sentence-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/sentence-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -5277,6 +6655,15 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -5290,6 +6677,30 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "integrity": "sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/snake-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/snake-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -5365,13 +6776,13 @@ } }, "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" @@ -5392,11 +6803,11 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -5441,6 +6852,35 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swap-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1", + "upper-case": "^1.1.1" + } + }, + "node_modules/swap-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -5625,6 +7065,31 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "node_modules/title-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", + "integrity": "sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.0.3" + } + }, + "node_modules/title-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/title-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -5823,6 +7288,21 @@ "node": ">=8" } }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "dev": true + }, + "node_modules/upper-case-first": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==", + "dev": true, + "dependencies": { + "upper-case": "^1.1.1" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6142,6 +7622,18 @@ "node": ">= 8" } }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -6205,6 +7697,29 @@ "node": ">= 6" } }, + "node_modules/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yarn": { + "version": "1.22.19", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.19.tgz", + "integrity": "sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "yarn": "bin/yarn.js", + "yarnpkg": "bin/yarn.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -6889,6 +8404,25 @@ } } }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "peer": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true, + "peer": true + }, "@nestjs/cli": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.1.0.tgz", @@ -7142,6 +8676,199 @@ } } }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@oclif/color": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@oclif/color/-/color-1.0.1.tgz", + "integrity": "sha512-qjYr+izgWdIVOroiBKqTzQgc1r5Wd9QB1J7yGM2EeelqhBARiiVLRZL45vhV4zdyTRdDkZS0EBzFwQap+nliLA==", + "dev": true, + "requires": { + "ansi-styles": "^4.2.1", + "chalk": "^4.1.0", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "tslib": "^2" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@oclif/core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-1.9.3.tgz", + "integrity": "sha512-npxWULRu+iW9AuUNoCH118MNI8cxYIkcWkknz3mCDumTo11FC+h3OY1cMtlclqZHfZcDHh4iaSkNMX/7se9GUQ==", + "dev": true, + "requires": { + "@oclif/linewrap": "^1.0.0", + "@oclif/screen": "^3.0.2", + "ansi-escapes": "^4.3.2", + "ansi-styles": "^4.3.0", + "cardinal": "^2.1.1", + "chalk": "^4.1.2", + "clean-stack": "^3.0.1", + "cli-progress": "^3.10.0", + "debug": "^4.3.4", + "ejs": "^3.1.6", + "fs-extra": "^9.1.0", + "get-package-type": "^0.1.0", + "globby": "^11.1.0", + "hyperlinker": "^1.0.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "js-yaml": "^3.14.1", + "natural-orderby": "^2.0.3", + "object-treeify": "^1.1.33", + "password-prompt": "^1.1.2", + "semver": "^7.3.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "supports-hyperlinks": "^2.2.0", + "tslib": "^2.3.1", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "dev": true, + "requires": { + "escape-string-regexp": "4.0.0" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@oclif/linewrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@oclif/linewrap/-/linewrap-1.0.0.tgz", + "integrity": "sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw==", + "dev": true + }, + "@oclif/plugin-help": { + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-5.1.12.tgz", + "integrity": "sha512-HvH/RubJxqCinP0vUWQLTOboT+SfjfL8h40s+PymkWaldIcXlpoRaJX50vz+SjZIs7uewZwEk8fzLqpF/BWXlg==", + "dev": true, + "requires": { + "@oclif/core": "^1.3.6" + } + }, + "@oclif/plugin-plugins": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@oclif/plugin-plugins/-/plugin-plugins-2.1.0.tgz", + "integrity": "sha512-Bgt+QpTlX7+Q0HkVgtbUGYQlo/hyzNBAaXH5l16ou9Ji5wfi5T+niV5AzQ14R7JF8ZDOTbUOU/NRBJ2bzLCaZQ==", + "dev": true, + "requires": { + "@oclif/color": "^1.0.1", + "@oclif/core": "^1.2.0", + "chalk": "^4.1.2", + "debug": "^4.1.0", + "fs-extra": "^9.0", + "http-call": "^5.2.2", + "load-json-file": "^5.3.0", + "npm-run-path": "^4.0.1", + "semver": "^7.3.2", + "tslib": "^2.0.0", + "yarn": "^1.22.17" + }, + "dependencies": { + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } + } + }, + "@oclif/screen": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.2.tgz", + "integrity": "sha512-S/SF/XYJeevwIgHFmVDAFRUvM3m+OjhvCAYMk78ZJQCYCQ5wS7j+LTt1ZEv2jpEEGg2tx/F6TYYWxddNAYHrFQ==", + "dev": true + }, + "@tooljet/cli": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@tooljet/cli/-/cli-0.0.12.tgz", + "integrity": "sha512-vpLxiTiucpCItd2Fi2IPBeXNBBcqz/TzWPi0P53Uj1OPyQTZc5p4GAp32niJ5n3Hp/iu/j1viyxM4zaD+z/2qg==", + "dev": true, + "requires": { + "@oclif/core": "^1.6.0", + "@oclif/plugin-help": "^5.1.12", + "@oclif/plugin-plugins": "^2.1.0", + "@types/inquirer": "^8.1.3", + "hygen": "^6.2.0", + "inquirer": "^7.3.3", + "rimraf": "^3.0.2" + } + }, "@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -7191,15 +8918,25 @@ "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", "dev": true }, + "@types/inquirer": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.1.tgz", + "integrity": "sha512-wKW3SKIUMmltbykg4I5JzCVzUhkuD9trD6efAmYgN2MrSntY0SMRQzEnD3mkyJ/rv9NLbTC7g3hKKE86YwEDLw==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, "@types/json-schema": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz", "integrity": "sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==" }, "@types/node": { - "version": "16.4.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.4.tgz", - "integrity": "sha512-BH/jX0HjzElFCQdAwaEMwuGBQwm6ViDZ00X6LKdnRRmGWOzkWugEH4+7a0BwfHQ8DfPPCSd/mdsm3Nu8FKFu0w==" + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, "@types/parse-json": { "version": "4.0.0", @@ -7216,6 +8953,15 @@ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==" }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -7398,9 +9144,9 @@ "dev": true }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "requires": {} }, @@ -7470,9 +9216,9 @@ } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -7482,6 +9228,12 @@ "color-convert": "^2.0.1" } }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, "arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -7501,6 +9253,12 @@ "sprintf-js": "~1.0.2" } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -7520,9 +9278,9 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, "asynckit": { "version": "0.4.0", @@ -7743,20 +9501,111 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001234.tgz", "integrity": "sha512-a3gjUVKkmwLdNysa1xkUAwN2VfJUJyVW47rsi3aCbkRCtbHAfo+rOsCqVw29G6coQ8gzAPb5XBXwiGHwme3isA==" }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, + "change-case": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.1.0.tgz", + "integrity": "sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==", + "dev": true, + "requires": { + "camel-case": "^3.0.0", + "constant-case": "^2.0.0", + "dot-case": "^2.1.0", + "header-case": "^1.0.0", + "is-lower-case": "^1.1.0", + "is-upper-case": "^1.1.0", + "lower-case": "^1.1.1", + "lower-case-first": "^1.0.0", + "no-case": "^2.3.2", + "param-case": "^2.1.0", + "pascal-case": "^2.0.0", + "path-case": "^2.1.0", + "sentence-case": "^2.1.0", + "snake-case": "^2.1.0", + "swap-case": "^1.1.0", + "title-case": "^2.1.0", + "upper-case": "^1.1.1", + "upper-case-first": "^1.1.0" + }, + "dependencies": { + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "dot-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", + "integrity": "sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "pascal-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", + "integrity": "sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==", + "dev": true, + "requires": { + "camel-case": "^3.0.0", + "upper-case-first": "^1.1.0" + } + } + } + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -7799,6 +9648,15 @@ "restore-cursor": "^3.1.0" } }, + "cli-progress": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.11.1.tgz", + "integrity": "sha512-TTMA2LHrYaZeNMcgZGO10oYqj9hvd03pltNtVbu4ddeyDTHlYV7gWxsFiuvaQlgwMBFCv1TukcjiODWFlb16tQ==", + "dev": true, + "requires": { + "string-width": "^4.2.3" + } + }, "cli-spinners": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", @@ -7926,6 +9784,22 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "constant-case": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", + "integrity": "sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==", + "dev": true, + "requires": { + "snake-case": "^2.1.0", + "upper-case": "^1.1.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -8123,9 +9997,9 @@ "integrity": "sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==" }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -8149,6 +10023,12 @@ "clone": "^1.0.2" } }, + "degit": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/degit/-/degit-2.8.4.tgz", + "integrity": "sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng==", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -8159,6 +10039,15 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -8233,6 +10122,15 @@ "safer-buffer": "^2.1.0" } }, + "ejs": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", + "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, "electron-to-chromium": { "version": "1.3.749", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.749.tgz", @@ -8548,6 +10446,25 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8565,6 +10482,15 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -8590,6 +10516,35 @@ "flat-cache": "^3.0.4" } }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -8747,6 +10702,15 @@ "mime-types": "^2.1.12" } }, + "front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1" + } + }, "fs-extra": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", @@ -8790,6 +10754,12 @@ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -8849,9 +10819,9 @@ } }, "globals": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz", - "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==", + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -8865,6 +10835,28 @@ } } }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -8903,6 +10895,33 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "header-case": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", + "integrity": "sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.3" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + } + } + }, "html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", @@ -8951,6 +10970,32 @@ "entities": "^2.0.0" } }, + "http-call": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/http-call/-/http-call-5.3.0.tgz", + "integrity": "sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==", + "dev": true, + "requires": { + "content-type": "^1.0.4", + "debug": "^4.1.1", + "is-retry-allowed": "^1.1.0", + "is-stream": "^2.0.0", + "parse-json": "^4.0.0", + "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -8972,6 +11017,243 @@ "integrity": "sha512-8yKEWNX4z2YsofXAMT7KvA1g8p+GxtB1ffV8XtpAEGuXNAbCV5wdNKH+qTpw8SM9fh4aMPDR+yQuKfgnreyZlg==", "dev": true }, + "hygen": { + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/hygen/-/hygen-6.2.7.tgz", + "integrity": "sha512-JKVS3cnOgKb+iY2/qP+6tq07P4U/gCmE2gmkSLDCoCGJONt2YCXimNT/2mzi2NuSzvisjjkVxwBY7kNvUufrAQ==", + "dev": true, + "requires": { + "@types/node": "^17.0.19", + "chalk": "^4.1.2", + "change-case": "^3.1.0", + "debug": "^4.3.3", + "degit": "^2.8.4", + "ejs": "^3.1.6", + "enquirer": "^2.3.6", + "eslint-plugin-prettier": "^4.0.0", + "execa": "^5.0.0", + "front-matter": "^4.0.2", + "fs-extra": "^10.0.0", + "ignore-walk": "^4.0.1", + "inflection": "^1.12.0", + "ora": "^5.0.0", + "yargs-parser": "^21.0.0" + }, + "dependencies": { + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "peer": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "peer": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true + }, + "eslint": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.18.0.tgz", + "integrity": "sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA==", + "dev": true, + "peer": true, + "requires": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "peer": true + } + } + }, + "eslint-plugin-prettier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "peer": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "peer": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "peer": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "peer": true + } + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "peer": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "peer": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "hyperlinker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz", + "integrity": "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -8991,6 +11273,15 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "ignore-walk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz", + "integrity": "sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -9021,6 +11312,12 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" }, + "inflection": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.2.tgz", + "integrity": "sha512-cmZlljCRTBFouT8UzMzrGcVEvkv6D/wBdcdKG7J1QH5cXjtU75Dm+P27v9EKu/Y43UYyCJd1WC4zLebRrC8NBw==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -9101,6 +11398,12 @@ "has": "^1.0.3" } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -9112,9 +11415,9 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { "is-extglob": "^2.1.1" } @@ -9133,6 +11436,23 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" }, + "is-lower-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==", + "dev": true, + "requires": { + "lower-case": "^1.1.0" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + } + } + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -9164,6 +11484,12 @@ "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", "dev": true }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -9179,6 +11505,24 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" }, + "is-upper-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==", + "dev": true, + "requires": { + "upper-case": "^1.1.0" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -9201,6 +11545,18 @@ "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", "peer": true }, + "jake": { + "version": "10.8.5", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", + "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + } + }, "jest-worker": { "version": "27.0.6", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz", @@ -9448,6 +11804,43 @@ } } }, + "load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } + } + }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -9490,6 +11883,13 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "peer": true + }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -9557,6 +11957,23 @@ "tslib": "^2.0.3" } }, + "lower-case-first": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==", + "dev": true, + "requires": { + "lower-case": "^1.1.2" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + } + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -9596,6 +12013,12 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", @@ -9625,9 +12048,9 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -9653,11 +12076,23 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "natural-orderby": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz", + "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -9708,6 +12143,12 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9854,6 +12295,73 @@ "tslib": "^2.0.3" } }, + "password-prompt": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/password-prompt/-/password-prompt-1.1.2.tgz", + "integrity": "sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.1.0", + "cross-spawn": "^6.0.5" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "path": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", @@ -9881,6 +12389,32 @@ } } }, + "path-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz", + "integrity": "sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==", + "dev": true, + "requires": { + "no-case": "^2.2.0" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + } + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -9950,6 +12484,22 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "peer": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -10006,6 +12556,12 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "ramda": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", @@ -10037,6 +12593,15 @@ "resolve": "^1.1.6" } }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -10044,9 +12609,9 @@ "peer": true }, "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "relateurl": { @@ -10138,6 +12703,12 @@ "signal-exit": "^3.0.2" } }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -10151,11 +12722,19 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "rxjs": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", - "peer": true, "requires": { "tslib": "^2.1.0" } @@ -10181,9 +12760,9 @@ } }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "requires": { "lru-cache": "^6.0.0" } @@ -10194,6 +12773,33 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "sentence-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", + "integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case-first": "^1.1.2" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + } + } + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -10239,6 +12845,12 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -10249,6 +12861,32 @@ "is-fullwidth-code-point": "^3.0.0" } }, + "snake-case": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "integrity": "sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==", + "dev": true, + "requires": { + "no-case": "^2.2.0" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + } + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -10310,13 +12948,13 @@ "dev": true }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "stringify-object": { @@ -10331,11 +12969,11 @@ } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -10362,6 +13000,34 @@ "has-flag": "^4.0.0" } }, + "supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "swap-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1", + "upper-case": "^1.1.1" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + } + } + }, "symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -10499,6 +13165,33 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "title-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", + "integrity": "sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.0.3" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + } + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -10629,6 +13322,21 @@ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==" }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "dev": true + }, + "upper-case-first": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==", + "dev": true, + "requires": { + "upper-case": "^1.1.1" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -10857,6 +13565,15 @@ "isexe": "^2.0.0" } }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -10902,6 +13619,18 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true + }, + "yarn": { + "version": "1.22.19", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.19.tgz", + "integrity": "sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ==", + "dev": true + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index be54ebca85..e5bcb515fe 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,7 @@ { + "name": "tooljet", + "version": "1.18.0", + "description": "ToolJet is an open-source low-code framework to build and deploy internal tools.", "engines": { "node": "14.17.3", "npm": "7.20.0" @@ -21,6 +24,7 @@ "@babel/preset-env": "^7.4.3", "@babel/preset-react": "^7.0.0", "@cypress/webpack-preprocessor": "^5.11.1", + "@tooljet/cli": "^0.0.12", "babel-loader": "^8.0.5", "eslint": "^7.25.0", "faker": "^5.5.3", diff --git a/plugins/packages/bigquery/lib/index.ts b/plugins/packages/bigquery/lib/index.ts index 9b41b6fa99..d497a896bf 100644 --- a/plugins/packages/bigquery/lib/index.ts +++ b/plugins/packages/bigquery/lib/index.ts @@ -12,15 +12,45 @@ export default class Bigquery implements QueryService { try { switch (operation) { case 'list_datasets': { - const [datasets] = await client.getDatasets(this.parseJSON(queryOptions.options)); + const [datasets] = await client.getDatasets(); result = datasets; break; } + case 'list_tables': { - const [tables] = await client.dataset(queryOptions.datasetId).getTables(this.parseJSON(queryOptions.options)); + const [tables] = await client.dataset(queryOptions.datasetId).getTables(); result = tables; break; } + + case 'create_table': { + const [table] = await client + .dataset(queryOptions.datasetId) + .createTable(queryOptions.tableId, this.parseJSON(queryOptions.options)); + result = table; + break; + } + + case 'delete_table': { + result = await client.dataset(queryOptions.datasetId).table(queryOptions.tableId).delete(); + break; + } + + case 'create_view': { + const query = `CREATE VIEW ${queryOptions.datasetId}.${queryOptions.view_name} AS + SELECT ${queryOptions.viewcolumns} + FROM ${queryOptions.datasetId}.${queryOptions.tableId} + WHERE ${queryOptions.condition};`; + + const [job] = await client.createQueryJob({ + ...this.parseJSON(queryOptions.queryOptions), + query: query, + }); + const [rows] = await job.getQueryResults(this.parseJSON(queryOptions.queryResultsOptions)); + result = rows; + break; + } + case 'query': { const [job] = await client.createQueryJob({ ...this.parseJSON(queryOptions.queryOptions), @@ -30,6 +60,44 @@ export default class Bigquery implements QueryService { result = rows; break; } + + case 'delete_record': { + const query = `DELETE FROM ${queryOptions.datasetId}.${queryOptions.tableId} ${ + queryOptions.condition ? `WHERE ${queryOptions.condition}` : 'WHERE TRUE' + }`; + const [job] = await client.createQueryJob({ + ...this.parseJSON(queryOptions.queryOptions), + query: query, + }); + const [rows] = await job.getQueryResults(this.parseJSON(queryOptions.queryResultsOptions)); + result = rows; + break; + } + + case 'insert_record': { + const rows = await client + .dataset(queryOptions.datasetId) + .table(queryOptions.tableId) + .insert(this.parseJSON(queryOptions.rows)); + result = rows; + break; + } + + case 'update_record': { + let columString = ''; + columString = await this.columnBuilder(queryOptions); + const query = `UPDATE ${queryOptions.datasetId}.${queryOptions.tableId} SET ${columString} ${ + queryOptions.condition ? `WHERE ${queryOptions.condition}` : 'WHERE TRUE' + }`; + + const [job] = await client.createQueryJob({ + ...this.parseJSON(queryOptions.queryOptions), + query: query, + }); + const [rows] = await job.getQueryResults(this.parseJSON(queryOptions.queryResultsOptions)); + result = rows; + break; + } } } catch (error) { console.log(error); @@ -41,6 +109,15 @@ export default class Bigquery implements QueryService { data: result, }; } + async columnBuilder(queryOptions: any): Promise { + const columString = []; + const columns = queryOptions.columns; + for (const [key, value] of Object.entries(columns)) { + const primaryKeyValue = typeof value === 'string' ? `'${value}'` : value; + columString.push(`${key}=${primaryKeyValue}`); + } + return columString.join(','); + } async getConnection(sourceOptions: any, _options?: object): Promise { const privateKey = this.getPrivateKey(sourceOptions?.private_key); diff --git a/plugins/packages/bigquery/lib/operations.json b/plugins/packages/bigquery/lib/operations.json index 56e5ccffb0..0f36598900 100644 --- a/plugins/packages/bigquery/lib/operations.json +++ b/plugins/packages/bigquery/lib/operations.json @@ -11,6 +11,10 @@ "type": "dropdown-component-flip", "description": "Single select dropdown for operation", "list": [ + { + "value": "query", + "name": "Query" + }, { "value": "list_datasets", "name": "List Datasets" @@ -20,18 +24,136 @@ "name": "List Tables" }, { - "value": "query", - "name": "Query" + "value": "create_table", + "name": "Create Table" + }, + { + "value": "delete_table", + "name": "Delete Table" + }, + { + "value": "create_view", + "name": "Create View" + }, + { + "value": "insert_record", + "name": "Insert Record" + }, + { + "value": "delete_record", + "name": "Delete Record" + }, + { + "value": "update_record", + "name": "Update Record" } ] }, - "list_tables": { + + "insert_record": { + "tableId": { + "label": "Table id", + "key": "tableId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter table id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter table id" + }, "datasetId": { - "label": "Dataset", + "label": "Dataset id", "key": "datasetId", "type": "codehinter", "lineNumbers": false, - "description": "Enter dataset", + "description": "Enter dataset id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter dataset" + }, + "rows": { + "label": "Rows", + "key": "rows", + "type": "codehinter", + "mode": "javascript", + "placeholder": "[{name: 'Tom', age: 30},{name: 'Jane', age: 32}]", + "description": "Enter rows", + "height": "150px" + } + }, + + "delete_record": { + "tableId": { + "label": "Table id", + "key": "tableId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter table id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter table id" + }, + "datasetId": { + "label": "Dataset id", + "key": "datasetId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter dataset id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter dataset" + }, + "condition": { + "label": "Condition", + "key": "condition", + "type": "codehinter", + "mode": "javascript", + "placeholder": "CustomerName='Alfreds Futterkiste'", + "description": "Enter condition", + "height": "150px" + }, + "queryOptions": { + "label": "Query options", + "key": "queryOptions", + "type": "codehinter", + "mode": "javascript", + "placeholder": "{ location: 'US', dryRun: true }", + "description": "Enter query options", + "height": "150px" + }, + "queryResultsOptions": { + "label": "Query results options", + "key": "queryResultsOptions", + "type": "codehinter", + "mode": "javascript", + "placeholder": "{ wrapIntegers: true }", + "description": "Enter Query results options", + "height": "150px" + } + }, + + "create_table": { + "tableId": { + "label": "Table id", + "key": "tableId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter table id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter table id" + }, + "datasetId": { + "label": "Dataset id", + "key": "datasetId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter dataset id", "width": "320px", "height": "36px", "className": "codehinter-plugins", @@ -42,22 +164,168 @@ "key": "options", "type": "codehinter", "mode": "javascript", - "placeholder": "{ filter: 'labels.color:green' }", + "placeholder": "{schema: [{name: 'Name', type: 'STRING', mode: 'REQUIRED'},{name: 'Age', type: 'INTEGER'}], location: 'US' }", "description": "Enter options", "height": "150px" } }, - "list_datasets": { - "options": { - "label": "Options", - "key": "options", + "delete_table": { + "tableId": { + "label": "Table id", + "key": "tableId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter table id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter table id" + }, + "datasetId": { + "label": "Dataset id", + "key": "datasetId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter dataset id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter dataset id" + } + }, + "create_view": { + "tableId": { + "label": "Table id", + "key": "tableId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter table id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter table id" + }, + "datasetId": { + "label": "Dataset id", + "key": "datasetId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter dataset id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter dataset" + }, + "view_name": { + "label": "View name", + "key": "view_name", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter view name", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter view name" + }, + "viewcolumns": { + "label": "View columns", + "key": "viewcolumns", + "type": "codehinter", + "placeholder": "column1, column2, ... ", + "description": "Enter viewcolumns", + "height": "150px" + }, + "condition": { + "label": "Condition", + "key": "condition", "type": "codehinter", "mode": "javascript", - "placeholder": "{ filter: 'labels.color:green' }", - "description": "Enter options", + "placeholder": "CustomerName='Alfreds Futterkiste'", + "description": "Enter condition", + "height": "150px" + }, + "queryOptions": { + "label": "Query options", + "key": "queryOptions", + "type": "codehinter", + "mode": "javascript", + "placeholder": "{ location: 'US', dryRun: true }", + "description": "Enter query options", + "height": "150px" + }, + "queryResultsOptions": { + "label": "Query results options", + "key": "queryResultsOptions", + "type": "codehinter", + "mode": "javascript", + "placeholder": "{ wrapIntegers: true }", + "description": "Enter Query results options", "height": "150px" } }, + "update_record": { + "tableId": { + "label": "Table id", + "key": "tableId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter table id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter table id" + }, + "datasetId": { + "label": "Dataset id", + "key": "datasetId", + "type": "codehinter", + "lineNumbers": false, + "description": "Enter dataset id", + "width": "320px", + "height": "36px", + "className": "codehinter-plugins", + "placeholder": "Enter dataset" + }, + "columns": { + "label": "Columns", + "key": "columns", + "type": "codehinter", + "placeholder": "{{({name:'bob',age:30})}}", + "description": "Enter columns", + "height": "150px" + }, + "condition": { + "label": "Condition", + "key": "condition", + "type": "codehinter", + "mode": "javascript", + "placeholder": "CustomerName='Alfreds Futterkiste'", + "description": "Enter condition", + "height": "150px" + }, + "queryOptions": { + "label": "Query options", + "key": "queryOptions", + "type": "codehinter", + "mode": "javascript", + "placeholder": "{ location: 'US', dryRun: true }", + "description": "Enter query options", + "height": "150px" + }, + "queryResultsOptions": { + "label": "Query results options", + "key": "queryResultsOptions", + "type": "codehinter", + "mode": "javascript", + "placeholder": "{ wrapIntegers: true }", + "description": "Enter Query results options", + "height": "150px" + } + }, + "list_datasets": { + }, + "list_tables": { + }, "query": { "query": { "label": "Query", @@ -88,4 +356,4 @@ } } } -} \ No newline at end of file +} diff --git a/plugins/packages/bigquery/lib/types.ts b/plugins/packages/bigquery/lib/types.ts index 78509ceba9..37f8ba2f99 100644 --- a/plugins/packages/bigquery/lib/types.ts +++ b/plugins/packages/bigquery/lib/types.ts @@ -8,4 +8,11 @@ export type QueryOptions = { datasetId: string; queryOptions: string; queryResultsOptions: string; + tableId: string; + rows: string; + condition: string; + columns: object; + viewcolumns: string; + values: string; + view_name: string; }; diff --git a/plugins/packages/woocommerce/lib/operations.json b/plugins/packages/woocommerce/lib/operations.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server/.version b/server/.version index 507266ba01..815d5ca06d 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -1.17.1 \ No newline at end of file +1.19.0 diff --git a/server/ee/services/oauth/oauth.service.ts b/server/ee/services/oauth/oauth.service.ts index 20fd47e6fd..acc8660150 100644 --- a/server/ee/services/oauth/oauth.service.ts +++ b/server/ee/services/oauth/oauth.service.ts @@ -48,20 +48,29 @@ export class OauthService { } async #findOrCreateUser({ firstName, lastName, email }: UserResponse, organization: Organization): Promise { - const { user, newUserCreated } = await this.usersService.findOrCreateByEmail( - { firstName, lastName, email }, - organization.id - ); + const existingUser = await this.usersService.findByEmail(email, organization.id, ['active', 'invited']); + const organizationUser = existingUser?.organizationUsers?.[0]; - if (newUserCreated) { - const organizationUser = await this.organizationUsersService.create(user, organization); - await this.organizationUsersService.activate(organizationUser); + if (!organizationUser) { + // User not exist in the workspace + const { user, newUserCreated } = await this.usersService.findOrCreateByEmail( + { firstName, lastName, email }, + organization.id + ); + + if (newUserCreated) { + const organizationUser = await this.organizationUsersService.create(user, organization); + await this.organizationUsersService.activate(organizationUser); + } + return user; + } else { + if (organizationUser.status !== 'active') await this.organizationUsersService.activate(organizationUser); + return existingUser; } - return user; } async #findAndActivateUser(email: string, organizationId: string): Promise { - const user = await this.usersService.findByEmail(email, organizationId); + const user = await this.usersService.findByEmail(email, organizationId, ['active', 'invited']); if (!user) { throw new UnauthorizedException('User does not exist in the workspace'); } @@ -70,7 +79,7 @@ export class OauthService { if (!organizationUser) { throw new UnauthorizedException('User does not exist in the workspace'); } - if (organizationUser.status != 'active') await this.organizationUsersService.activate(organizationUser); + if (organizationUser.status !== 'active') await this.organizationUsersService.activate(organizationUser); return user; } diff --git a/server/migrations/1653391166172-AddFolderUpdateAndDeletePermissionsToGroupPermissions.ts b/server/migrations/1653391166172-AddFolderUpdateAndDeletePermissionsToGroupPermissions.ts new file mode 100644 index 0000000000..ed8f444f07 --- /dev/null +++ b/server/migrations/1653391166172-AddFolderUpdateAndDeletePermissionsToGroupPermissions.ts @@ -0,0 +1,30 @@ +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +export class AddFolderUpdateAndDeletePermissionToGroupPermissions1653391166172 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + 'group_permissions', + new TableColumn({ + name: 'folder_delete', + type: 'boolean', + default: false, + isNullable: false, + }) + ); + + await queryRunner.addColumn( + 'group_permissions', + new TableColumn({ + name: 'folder_update', + type: 'boolean', + default: false, + isNullable: false, + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn('group_permissions', 'folder_delete'); + await queryRunner.dropColumn('group_permissions', 'folder_update'); + } +} diff --git a/server/migrations/1653472569828-addedInstructionTextPropInFilePickerWidget.ts b/server/migrations/1653472569828-addedInstructionTextPropInFilePickerWidget.ts new file mode 100644 index 0000000000..9ec3b042c3 --- /dev/null +++ b/server/migrations/1653472569828-addedInstructionTextPropInFilePickerWidget.ts @@ -0,0 +1,42 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { AppVersion } from '../src/entities/app_version.entity'; + +export class addedInstructionTextPropInFilePickerWidget1653472569828 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const entityManager = queryRunner.manager; + const appVersions = await entityManager.find(AppVersion); + + for (const version of appVersions) { + const definition = version['definition']; + + if (definition) { + const components = definition['components']; + + for (const componentId of Object.keys(components)) { + const component = components[componentId]; + if (component.component.component === 'FilePicker') { + component.component.definition.properties.instructionText = { + value: 'Drag and Drop some files here, or click to select files', + }; + components[componentId] = { + ...component, + component: { + ...component.component, + definition: { + ...component.component.definition, + }, + }, + }; + } + } + + definition['components'] = components; + version.definition = definition; + + await entityManager.update(AppVersion, { id: version.id }, { definition }); + } + } + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/migrations/1653474337657-BackfillFolderDeleteFolderUpdatePermissionsAsTruthyForAdminGroup.ts b/server/migrations/1653474337657-BackfillFolderDeleteFolderUpdatePermissionsAsTruthyForAdminGroup.ts new file mode 100644 index 0000000000..d58310bc4b --- /dev/null +++ b/server/migrations/1653474337657-BackfillFolderDeleteFolderUpdatePermissionsAsTruthyForAdminGroup.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { GroupPermission } from '../src/entities/group_permission.entity'; + +export class BackfillFolderDeleteFolderUpdatePermissionsAsTruthyForAdminGroup1653474337657 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + const entityManager = queryRunner.manager; + const GroupPermissionRepostory = entityManager.getRepository(GroupPermission); + + await GroupPermissionRepostory.update({ group: 'admin' }, { folderUpdate: true, folderDelete: true }); + } + + public async down(queryRunner: QueryRunner): Promise { + const entityManager = queryRunner.manager; + const GroupPermissionRepostory = entityManager.getRepository(GroupPermission); + + await GroupPermissionRepostory.update({ group: 'admin' }, { folderUpdate: false, folderDelete: false }); + } +} diff --git a/server/migrations/1655279771926-listViewWidgetAddingBorderRadiusProperty.ts b/server/migrations/1655279771926-listViewWidgetAddingBorderRadiusProperty.ts new file mode 100644 index 0000000000..85e70cedbc --- /dev/null +++ b/server/migrations/1655279771926-listViewWidgetAddingBorderRadiusProperty.ts @@ -0,0 +1,42 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { AppVersion } from '../src/entities/app_version.entity'; + +export class listViewWidgetAddingBorderRadiusProperty1655279771926 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const entityManager = queryRunner.manager; + const appVersions = await entityManager.find(AppVersion); + + for (const version of appVersions) { + const definition = version['definition']; + + if (definition) { + const components = definition['components']; + + for (const componentId of Object.keys(components)) { + const component = components[componentId]; + if (component.component.component === 'Listview') { + component.component.definition.styles.borderRadius = { + value: 0, + }; + components[componentId] = { + ...component, + component: { + ...component.component, + definition: { + ...component.component.definition, + }, + }, + }; + } + } + + definition['components'] = components; + version.definition = definition; + + await entityManager.update(AppVersion, { id: version.id }, { definition }); + } + } + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/src/controllers/folders.controller.ts b/server/src/controllers/folders.controller.ts index 8a91585fce..1b4a93e952 100644 --- a/server/src/controllers/folders.controller.ts +++ b/server/src/controllers/folders.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Query, Request, UseGuards, Body } from '@nestjs/common'; +import { Controller, Get, Post, Query, Request, UseGuards, Body, Delete, Param, Put } from '@nestjs/common'; import { decamelizeKeys } from 'humps'; import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard'; import { FoldersService } from '../services/folders.service'; @@ -6,6 +6,7 @@ import { ForbiddenException } from '@nestjs/common'; import { FoldersAbilityFactory } from 'src/modules/casl/abilities/folders-ability.factory'; import { Folder } from 'src/entities/folder.entity'; import { CreateFolderDto } from '@dto/create-folder.dto'; +import { User } from 'src/decorators/user.decorator'; @Controller('folders') export class FoldersController { @@ -31,4 +32,28 @@ export class FoldersController { const folder = await this.foldersService.create(req.user, folderName); return decamelizeKeys(folder); } + + @UseGuards(JwtAuthGuard) + @Put(':id') + async update(@User() user, @Param('id') id, @Body('name') folderName: string) { + const ability = await this.foldersAbilityFactory.folderActions(user, {}); + + if (!ability.can('updateFolder', Folder)) { + throw new ForbiddenException('You do not have permissions to perform this action'); + } + const folder = await this.foldersService.update(id, folderName); + return decamelizeKeys(folder); + } + + @UseGuards(JwtAuthGuard) + @Delete(':id') + async delete(@User() user, @Param('id') id) { + const ability = await this.foldersAbilityFactory.folderActions(user, {}); + + if (!ability.can('deleteFolder', Folder)) { + throw new ForbiddenException('You do not have permissions to perform this action'); + } + + return await this.foldersService.delete(user, id); + } } diff --git a/server/src/controllers/organization_users.controller.ts b/server/src/controllers/organization_users.controller.ts index ba72c8fe96..a16a919173 100644 --- a/server/src/controllers/organization_users.controller.ts +++ b/server/src/controllers/organization_users.controller.ts @@ -29,17 +29,17 @@ export class OrganizationUsersController { @UseGuards(JwtAuthGuard, PoliciesGuard) @CheckPolicies((ability: AppAbility) => ability.can('archiveUser', UserEntity)) @Post(':id/archive') - async archive(@Param('id') id: string) { - const result = await this.organizationUsersService.archive(id); - return decamelizeKeys({ result }); + async archive(@User() user, @Param('id') id: string) { + await this.organizationUsersService.archive(id, user.organizationId); + return; } @UseGuards(JwtAuthGuard, PoliciesGuard) @CheckPolicies((ability: AppAbility) => ability.can('archiveUser', UserEntity)) @Post(':id/unarchive') async unarchive(@User() user, @Param('id') id: string) { - const result = await this.organizationUsersService.unarchive(user, id); - return decamelizeKeys({ result }); + await this.organizationUsersService.unarchive(user, id); + return; } // Deprecated diff --git a/server/src/entities/group_permission.entity.ts b/server/src/entities/group_permission.entity.ts index 0583ec6730..4bb08192b9 100644 --- a/server/src/entities/group_permission.entity.ts +++ b/server/src/entities/group_permission.entity.ts @@ -37,6 +37,12 @@ export class GroupPermission extends BaseEntity { @Column({ name: 'folder_create', default: false }) folderCreate: boolean; + @Column({ name: 'folder_delete', default: false }) + folderDelete: boolean; + + @Column({ name: 'folder_update', default: false }) + folderUpdate: boolean; + @CreateDateColumn({ default: () => 'now()', name: 'created_at' }) createdAt: Date; diff --git a/server/src/modules/auth/jwt.strategy.ts b/server/src/modules/auth/jwt.strategy.ts index 7417f5eaa1..be5652bf73 100644 --- a/server/src/modules/auth/jwt.strategy.ts +++ b/server/src/modules/auth/jwt.strategy.ts @@ -17,13 +17,12 @@ export class JwtStrategy extends PassportStrategy(Strategy) { async validate(payload: any) { if (!payload.organizationId) return false; - const user: User = await this.usersService.findByEmail(payload.sub, payload.organizationId); + const user: User = await this.usersService.findByEmail(payload.sub, payload.organizationId, 'active'); if (!user) return false; user.organizationId = payload.organizationId; user.isPasswordLogin = payload.isPasswordLogin; - if (user && (await this.usersService.status(user)) !== 'archived') return user; - else return false; + return user; } } diff --git a/server/src/modules/casl/abilities/folders-ability.factory.ts b/server/src/modules/casl/abilities/folders-ability.factory.ts index d25ace9708..59c43ec7f7 100644 --- a/server/src/modules/casl/abilities/folders-ability.factory.ts +++ b/server/src/modules/casl/abilities/folders-ability.factory.ts @@ -4,7 +4,7 @@ import { Injectable } from '@nestjs/common'; import { UsersService } from 'src/services/users.service'; import { Folder } from 'src/entities/folder.entity'; -type Actions = 'createFolder'; +type Actions = 'createFolder' | 'updateFolder' | 'deleteFolder'; type Subjects = InferSubjects | 'all'; @@ -21,6 +21,14 @@ export class FoldersAbilityFactory { can('createFolder', Folder); } + if (await this.usersService.userCan(user, 'update', 'Folder')) { + can('updateFolder', Folder); + } + + if (await this.usersService.userCan(user, 'delete', 'Folder')) { + can('deleteFolder', Folder); + } + return build({ detectSubjectType: (item) => item.constructor as ExtractSubjectType, }); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 2f08cfbc12..a6098290f6 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -50,13 +50,13 @@ export class AuthService { } private async validateUser(email: string, password: string, organizationId?: string): Promise { - const user = await this.usersService.findByEmail(email, organizationId); + const user = await this.usersService.findByEmail(email, organizationId, 'active'); - if (!user) return null; + if (!(user && (await bcrypt.compare(password, user.password)))) { + return; + } - const isVerified = await bcrypt.compare(password, user.password); - - return isVerified ? user : null; + return user; } async login(email: string, password: string, organizationId?: string) { @@ -64,76 +64,78 @@ export class AuthService { const user = await this.validateUser(email, password, organizationId); - if (user && (await this.usersService.status(user)) !== 'archived') { - if (!organizationId) { - // Global login - // Determine the organization to be loaded - if (this.configService.get('DISABLE_MULTI_WORKSPACE') === 'true') { - // Single organization - organization = await this.organizationsService.getSingleOrganization(); - if (!organization?.ssoConfigs?.find((oc) => oc.sso == 'form' && oc.enabled)) { - throw new UnauthorizedException(); - } - } else { - const organizationList: Organization[] = await this.organizationsService.findOrganizationSupportsFormLogin( - user - ); - - const defaultOrgDetails: Organization = organizationList?.find((og) => og.id === user.defaultOrganizationId); - // Multi organization - if (defaultOrgDetails) { - // default organization form login enabled - organization = defaultOrgDetails; - } else if (organizationList?.length > 0) { - // default organization form login not enabled, picking first one from form enabled list - organization = organizationList[0]; - } else { - // no form login enabled organization available for user - creating new one - organization = await this.organizationsService.create('Untitled workspace', user); - } - } - user.organizationId = organization.id; - } else { - // organization specific login - user.organizationId = organizationId; - - organization = await this.organizationsService.get(user.organizationId); - const formConfigs: SSOConfigs = organization?.ssoConfigs?.find((sso) => sso.sso === 'form'); - - if (!formConfigs?.enabled) { - // no configurations in organization side or Form login disabled for the organization - throw new UnauthorizedException('Password login is disabled for the organization'); - } - } - - if (user.defaultOrganizationId !== user.organizationId) { - // Updating default organization Id - await this.usersService.updateDefaultOrganization(user, organization.id); - } - - const payload = { - username: user.id, - sub: user.email, - organizationId: user.organizationId, - isPasswordLogin: true, - }; - - return decamelizeKeys({ - id: user.id, - auth_token: this.jwtService.sign(payload), - email: user.email, - first_name: user.firstName, - last_name: user.lastName, - avatar_id: user.avatarId, - organizationId: user.organizationId, - organization: organization.name, - admin: await this.usersService.hasGroup(user, 'admin'), - group_permissions: await this.usersService.groupPermissions(user), - app_group_permissions: await this.usersService.appGroupPermissions(user), - }); - } else { + if (!user) { throw new UnauthorizedException('Invalid credentials'); } + if (!organizationId) { + // Global login + // Determine the organization to be loaded + if (this.configService.get('DISABLE_MULTI_WORKSPACE') === 'true') { + // Single organization + if (user?.organizationUsers?.[0].status !== 'active') { + throw new UnauthorizedException('Your account is not active'); + } + organization = await this.organizationsService.getSingleOrganization(); + if (!organization?.ssoConfigs?.find((oc) => oc.sso == 'form' && oc.enabled)) { + throw new UnauthorizedException(); + } + } else { + // Multi organization + const organizationList: Organization[] = await this.organizationsService.findOrganizationSupportsFormLogin( + user + ); + + const defaultOrgDetails: Organization = organizationList?.find((og) => og.id === user.defaultOrganizationId); + if (defaultOrgDetails) { + // default organization form login enabled + organization = defaultOrgDetails; + } else if (organizationList?.length > 0) { + // default organization form login not enabled, picking first one from form enabled list + organization = organizationList[0]; + } else { + // no form login enabled organization available for user - creating new one + organization = await this.organizationsService.create('Untitled workspace', user); + } + } + user.organizationId = organization.id; + } else { + // organization specific login + // No need to validate user status, validateUser() already covers it + user.organizationId = organizationId; + + organization = await this.organizationsService.get(user.organizationId); + const formConfigs: SSOConfigs = organization?.ssoConfigs?.find((sso) => sso.sso === 'form'); + + if (!formConfigs?.enabled) { + // no configurations in organization side or Form login disabled for the organization + throw new UnauthorizedException('Password login is disabled for the organization'); + } + } + + if (user.defaultOrganizationId !== user.organizationId) { + // Updating default organization Id + await this.usersService.updateDefaultOrganization(user, organization.id); + } + + const payload = { + username: user.id, + sub: user.email, + organizationId: user.organizationId, + isPasswordLogin: true, + }; + + return decamelizeKeys({ + id: user.id, + auth_token: this.jwtService.sign(payload), + email: user.email, + first_name: user.firstName, + last_name: user.lastName, + organizationId: user.organizationId, + organization: organization.name, + admin: await this.usersService.hasGroup(user, 'admin'), + group_permissions: await this.usersService.groupPermissions(user), + app_group_permissions: await this.usersService.appGroupPermissions(user), + }); } async switchOrganization(newOrganizationId: string, user: User, isNewOrganization?: boolean) { @@ -143,45 +145,44 @@ export class AuthService { if (this.configService.get('DISABLE_MULTI_WORKSPACE') === 'true') { throw new UnauthorizedException(); } - const newUser = await this.usersService.findByEmail(user.email, newOrganizationId); + const newUser = await this.usersService.findByEmail(user.email, newOrganizationId, 'active'); - if (newUser && (await this.usersService.status(newUser)) !== 'archived') { - newUser.organizationId = newOrganizationId; - - const organization: Organization = await this.organizationsService.get(newUser.organizationId); - - const formConfigs: SSOConfigs = organization?.ssoConfigs?.find((sso) => sso.sso === 'form'); - - if (!formConfigs?.enabled) { - // no configurations in organization side or Form login disabled for the organization - throw new UnauthorizedException('Password login disabled for the organization'); - } - - // Updating default organization Id - await this.usersService.updateDefaultOrganization(newUser, newUser.organizationId); - - const payload = { - username: user.id, - sub: user.email, - organizationId: newUser.organizationId, - isPasswordLogin: true, - }; - - return decamelizeKeys({ - id: newUser.id, - auth_token: this.jwtService.sign(payload), - email: newUser.email, - first_name: newUser.firstName, - last_name: newUser.lastName, - organizationId: newUser.organizationId, - organization: organization.name, - admin: await this.usersService.hasGroup(newUser, 'admin'), - group_permissions: await this.usersService.groupPermissions(newUser), - app_group_permissions: await this.usersService.appGroupPermissions(newUser), - }); - } else { + if (!newUser) { throw new UnauthorizedException('Invalid credentials'); } + newUser.organizationId = newOrganizationId; + + const organization: Organization = await this.organizationsService.get(newUser.organizationId); + + const formConfigs: SSOConfigs = organization?.ssoConfigs?.find((sso) => sso.sso === 'form'); + + if (!formConfigs?.enabled) { + // no configurations in organization side or Form login disabled for the organization + throw new UnauthorizedException('Password login disabled for the organization'); + } + + // Updating default organization Id + await this.usersService.updateDefaultOrganization(newUser, newUser.organizationId); + + const payload = { + username: user.id, + sub: user.email, + organizationId: newUser.organizationId, + isPasswordLogin: true, + }; + + return decamelizeKeys({ + id: newUser.id, + auth_token: this.jwtService.sign(payload), + email: newUser.email, + first_name: newUser.firstName, + last_name: newUser.lastName, + organizationId: newUser.organizationId, + organization: organization.name, + admin: await this.usersService.hasGroup(newUser, 'admin'), + group_permissions: await this.usersService.groupPermissions(newUser), + app_group_permissions: await this.usersService.appGroupPermissions(newUser), + }); } async signup(email: string) { diff --git a/server/src/services/folders.service.ts b/server/src/services/folders.service.ts index 15c2588390..3a1c2282d0 100644 --- a/server/src/services/folders.service.ts +++ b/server/src/services/folders.service.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, ForbiddenException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { App } from 'src/entities/app.entity'; import { FolderApp } from 'src/entities/folder_app.entity'; import { UserGroupPermission } from 'src/entities/user_group_permission.entity'; -import { Brackets, createQueryBuilder, Repository } from 'typeorm'; +import { Brackets, createQueryBuilder, Repository, UpdateResult } from 'typeorm'; import { User } from '../../src/entities/user.entity'; import { Folder } from '../entities/folder.entity'; import { UsersService } from './users.service'; @@ -31,6 +31,10 @@ export class FoldersService { ); } + async update(folderId: string, folderName: string): Promise { + return this.foldersRepository.update({ id: folderId }, { name: folderName }); + } + async allFolders(user: User): Promise { if (await this.usersService.hasGroup(user, 'admin')) { return await this.foldersRepository.find({ @@ -292,4 +296,36 @@ export class FoldersService { return viewableAppsInFolder; } + + async delete(user: User, id: string) { + const folder = await this.foldersRepository.findOneOrFail({ id, organizationId: user.organizationId }); + const allViewableApps = await createQueryBuilder(App, 'apps') + .select('apps.id') + .innerJoin('apps.groupPermissions', 'group_permissions') + .innerJoin('apps.appGroupPermissions', 'app_group_permissions') + .innerJoin( + UserGroupPermission, + 'user_group_permissions', + 'app_group_permissions.group_permission_id = user_group_permissions.group_permission_id' + ) + .where('user_group_permissions.user_id = :userId', { userId: user.id }) + .andWhere('app_group_permissions.read = :value', { value: true }) + .orWhere('apps.user_id = :userId', { + value: true, + organizationId: user.organizationId, + userId: user.id, + }) + .getMany(); + + const allViewableAppIds = allViewableApps.map((app) => app.id); + + folder.folderApps.map((folderApp: FolderApp) => { + if (!allViewableAppIds.includes(folderApp.appId)) { + throw new ForbiddenException( + 'Applications not authorised for you are included in the folder, please contact administrator to remove them and try again' + ); + } + }); + return await this.foldersRepository.delete({ id, organizationId: user.organizationId }); + } } diff --git a/server/src/services/group_permissions.service.ts b/server/src/services/group_permissions.service.ts index 18f146ab9a..8e55147325 100644 --- a/server/src/services/group_permissions.service.ts +++ b/server/src/services/group_permissions.service.ts @@ -123,7 +123,17 @@ export class GroupPermissionsService { }, }); - const { app_create, app_delete, add_apps, remove_apps, add_users, remove_users, folder_create } = body; + const { + app_create, + app_delete, + add_apps, + remove_apps, + add_users, + remove_users, + folder_create, + folder_delete, + folder_update, + } = body; await getManager().transaction(async (manager) => { // update group permissions @@ -131,6 +141,8 @@ export class GroupPermissionsService { ...(typeof app_create === 'boolean' && { appCreate: app_create }), ...(typeof app_delete === 'boolean' && { appDelete: app_delete }), ...(typeof folder_create === 'boolean' && { folderCreate: folder_create }), + ...(typeof folder_delete === 'boolean' && { folderDelete: folder_delete }), + ...(typeof folder_update === 'boolean' && { folderUpdate: folder_update }), }; if (Object.keys(groupPermissionUpdateParams).length !== 0) { await manager.update(GroupPermission, groupPermissionId, groupPermissionUpdateParams); diff --git a/server/src/services/organization_users.service.ts b/server/src/services/organization_users.service.ts index 53a2998a31..8b35492f5d 100644 --- a/server/src/services/organization_users.service.ts +++ b/server/src/services/organization_users.service.ts @@ -1,13 +1,14 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from '../entities/user.entity'; -import { createQueryBuilder, getManager, Repository } from 'typeorm'; +import { createQueryBuilder, Repository } from 'typeorm'; import { UsersService } from 'src/services/users.service'; import { OrganizationUser } from 'src/entities/organization_user.entity'; import { BadRequestException } from '@nestjs/common'; import { EmailService } from './email.service'; import { Organization } from 'src/entities/organization.entity'; import { GroupPermission } from 'src/entities/group_permission.entity'; +import { ConfigService } from '@nestjs/config'; const uuid = require('uuid'); @Injectable() @@ -16,7 +17,8 @@ export class OrganizationUsersService { @InjectRepository(OrganizationUser) private organizationUsersRepository: Repository, private usersService: UsersService, - private emailService: EmailService + private emailService: EmailService, + private configService: ConfigService ) {} async findOrganization(id: string): Promise { @@ -49,61 +51,51 @@ export class OrganizationUsersService { return await this.organizationUsersRepository.update(id, { role }); } - async archive(id: string) { - await getManager().transaction(async (manager) => { - const organizationUser = await manager.findOne(OrganizationUser, { - where: { id }, - }); - const user = await manager.findOne(User, { - where: { id: organizationUser.userId }, - }); - - await this.usersService.throwErrorIfRemovingLastActiveAdmin(user, undefined, organizationUser.organizationId); - - await manager.update(OrganizationUser, id, { status: 'archived', invitationToken: null }); + async archive(id: string, organizationId: string): Promise { + const organizationUser = await this.organizationUsersRepository.findOneOrFail({ + where: { id, organizationId }, + relations: ['user'], }); - return true; + await this.usersService.throwErrorIfRemovingLastActiveAdmin(organizationUser?.user, undefined, organizationId); + await this.organizationUsersRepository.update(id, { status: 'archived', invitationToken: null }); } - async unarchive(user: User, id: string) { + async unarchive(user: User, id: string): Promise { const organizationUser = await this.organizationUsersRepository.findOne({ - where: { id }, + where: { id, organizationId: user.organizationId }, + relations: ['user', 'organization'], }); - if (organizationUser.status !== 'archived') return false; + + if (!(organizationUser && organizationUser.organization && organizationUser.user)) { + throw new BadRequestException('User not exist'); + } + if (organizationUser.status !== 'archived') { + throw new BadRequestException('User status must be archived to unarchive'); + } const invitationToken = uuid.v4(); - await getManager().transaction(async (manager) => { - await manager.update(OrganizationUser, organizationUser.id, { - status: 'invited', - invitationToken, - }); - await manager.update(User, organizationUser.userId, { password: uuid.v4() }); - }); + await this.organizationUsersRepository.update(id, { status: 'invited', invitationToken }); - const updatedUser = await this.usersService.findOne(organizationUser.userId); - - const currentOrganization: Organization = ( - await this.organizationUsersRepository.findOne({ - where: { userId: user.id, organizationId: user.organizationId }, - relations: ['organization'], - }) - )?.organization; + if (this.configService.get('DISABLE_MULTI_WORKSPACE') === 'true') { + // Resetting password if single organization + await this.usersService.updateUser(id, { password: uuid.v4() }); + } await this.emailService.sendOrganizationUserWelcomeEmail( - updatedUser.email, - updatedUser.firstName, + organizationUser.user.email, + organizationUser.user.firstName, user.firstName, invitationToken, - currentOrganization.name + organizationUser.organization.name ); - return true; + return; } - async activate(user: OrganizationUser) { - await this.organizationUsersRepository.update(user.id, { + async activate(organizationUser: OrganizationUser) { + await this.organizationUsersRepository.update(organizationUser.id, { status: 'active', }); } diff --git a/server/src/services/organizations.service.ts b/server/src/services/organizations.service.ts index 22110b747c..eaa55ec477 100644 --- a/server/src/services/organizations.service.ts +++ b/server/src/services/organizations.service.ts @@ -93,6 +93,8 @@ export class OrganizationsService { appCreate: isAdmin, appDelete: isAdmin, folderCreate: isAdmin, + folderUpdate: isAdmin, + folderDelete: isAdmin, }); await this.groupPermissionsRepository.save(groupPermission); createdGroupPermissions.push(groupPermission); @@ -330,7 +332,7 @@ export class OrganizationsService { }; let user = await this.usersService.findByEmail(userParams.email); - let defaultOrganisation: Organization, + let defaultOrganization: Organization, shouldSendWelcomeMail = false; if (user?.organizationUsers?.some((ou) => ou.organizationId === currentUser.organizationId)) { @@ -346,7 +348,7 @@ export class OrganizationsService { // User not exist shouldSendWelcomeMail = true; // Create default organization - defaultOrganisation = await this.create('Untitled workspace'); + defaultOrganization = await this.create('Untitled workspace'); } user = await this.usersService.create( userParams, @@ -354,13 +356,13 @@ export class OrganizationsService { ['all_users'], user, true, - defaultOrganisation?.id + defaultOrganization?.id ); - if (defaultOrganisation) { + if (defaultOrganization) { // Setting up default organization - await this.organizationUserService.create(user, defaultOrganisation, true); - await this.usersService.attachUserGroup(['all_users', 'admin'], defaultOrganisation.id, user.id); + await this.organizationUserService.create(user, defaultOrganization, true); + await this.usersService.attachUserGroup(['all_users', 'admin'], defaultOrganization.id, user.id); } const currentOrganization: Organization = ( diff --git a/server/src/services/seeds.service.ts b/server/src/services/seeds.service.ts index 35958b2c8d..2a6d1f9d7c 100644 --- a/server/src/services/seeds.service.ts +++ b/server/src/services/seeds.service.ts @@ -75,6 +75,8 @@ export class SeedsService { appCreate: group == 'admin', appDelete: group == 'admin', folderCreate: group == 'admin', + folderUpdate: group == 'admin', + folderDelete: group == 'admin', }); await manager.save(groupPermission); diff --git a/server/src/services/users.service.ts b/server/src/services/users.service.ts index 92dfbe5839..9503ca41c2 100644 --- a/server/src/services/users.service.ts +++ b/server/src/services/users.service.ts @@ -31,12 +31,13 @@ export class UsersService { return this.usersRepository.findOne({ where: { id } }); } - async findByEmail(email: string, organisationId?: string): Promise { + async findByEmail(email: string, organisationId?: string, status?: string | Array): Promise { if (!organisationId) { return this.usersRepository.findOne({ where: { email }, }); } else { + const statusList = status ? (typeof status === 'object' ? status : [status]) : ['active', 'invited', 'archived']; return await createQueryBuilder(User, 'users') .innerJoinAndSelect( 'users.organizationUsers', @@ -44,7 +45,9 @@ export class UsersService { 'organization_users.organizationId = :organisationId', { organisationId } ) - .where('organization_users.status = :active', { active: 'active' }) + .where('organization_users.status IN(:...statusList)', { + statusList, + }) .andWhere('users.email = :email', { email }) .getOne(); } @@ -115,11 +118,6 @@ export class UsersService { }); } - async status(user: User) { - const orgUser = await this.organizationUsersRepository.findOne({ where: { user } }); - return orgUser.status; - } - async findOrCreateByEmail(userParams: any, organizationId: string): Promise<{ user: User; newUserCreated: boolean }> { let user: User; let newUserCreated = false; @@ -147,7 +145,7 @@ export class UsersService { const hashedPassword = password ? bcrypt.hashSync(password, 10) : undefined; - const updateableParams = { + const updatableParams = { forgotPasswordToken, firstName, lastName, @@ -155,16 +153,14 @@ export class UsersService { }; // removing keys with undefined values - cleanObject(updateableParams); + cleanObject(updatableParams); let user: User; const performUpdateInTransaction = async (manager) => { - await manager.update(User, userId, { ...updateableParams }); + await manager.update(User, userId, updatableParams); user = await manager.findOne(User, { where: { id: userId } }); - await this.removeUserGroupPermissionsIfExists(manager, user, removeGroups, organizationId); - await this.addUserGroupPermissions(manager, user, addGroups, organizationId); }; @@ -179,6 +175,10 @@ export class UsersService { return user; } + async updateUser(userId, updatableParams) { + await this.usersRepository.update(userId, updatableParams); + } + async addUserGroupPermissions(manager: EntityManager, user: User, addGroups: string[], organizationId?: string) { const orgId = organizationId || user.defaultOrganizationId; if (addGroups) { @@ -313,6 +313,12 @@ export class UsersService { case 'create': permissionGrant = this.canAnyGroupPerformAction('folderCreate', await this.groupPermissions(user)); break; + case 'update': + permissionGrant = this.canAnyGroupPerformAction('folderUpdate', await this.groupPermissions(user)); + break; + case 'delete': + permissionGrant = this.canAnyGroupPerformAction('folderDelete', await this.groupPermissions(user)); + break; default: permissionGrant = false; break; diff --git a/server/test/controllers/app.e2e-spec.ts b/server/test/controllers/app.e2e-spec.ts index c82950c08c..2c773b5046 100644 --- a/server/test/controllers/app.e2e-spec.ts +++ b/server/test/controllers/app.e2e-spec.ts @@ -76,11 +76,15 @@ describe('Authentication', () => { expect(adminGroup.appCreate).toBeTruthy(); expect(adminGroup.appDelete).toBeTruthy(); expect(adminGroup.folderCreate).toBeTruthy(); + expect(adminGroup.folderUpdate).toBeTruthy(); + expect(adminGroup.folderDelete).toBeTruthy(); const allUserGroup = groupPermissions.find((x) => x.group == 'all_users'); expect(allUserGroup.appCreate).toBeFalsy(); expect(allUserGroup.appDelete).toBeFalsy(); expect(allUserGroup.folderCreate).toBeFalsy(); + expect(allUserGroup.folderUpdate).toBeFalsy(); + expect(allUserGroup.folderDelete).toBeFalsy(); }); describe('Single organization operations', () => { beforeEach(async () => { @@ -126,6 +130,24 @@ describe('Authentication', () => { .set('Authorization', authHeaderForUser(adminUser)) .expect(401); }); + it('throw 401 if user is invited', async () => { + await createUser(app, { email: 'user@tooljet.io', status: 'invited' }); + + await request(app.getHttpServer()) + .post('/api/authenticate') + .send({ email: 'user@tooljet.io', password: 'password' }) + .expect(401); + + const adminUser = await userRepository.findOneOrFail({ + email: 'admin@tooljet.io', + }); + await orgUserRepository.update({ userId: adminUser.id }, { status: 'invited' }); + + await request(app.getHttpServer()) + .get('/api/organizations/users') + .set('Authorization', authHeaderForUser(adminUser)) + .expect(401); + }); it('throw 401 if invalid credentials', async () => { await request(app.getHttpServer()) .post('/api/authenticate') @@ -202,11 +224,15 @@ describe('Authentication', () => { expect(adminGroup.appCreate).toBeTruthy(); expect(adminGroup.appDelete).toBeTruthy(); expect(adminGroup.folderCreate).toBeTruthy(); + expect(adminGroup.folderUpdate).toBeTruthy(); + expect(adminGroup.folderDelete).toBeTruthy(); const allUserGroup = groupPermissions.find((x) => x.group == 'all_users'); expect(allUserGroup.appCreate).toBeFalsy(); expect(allUserGroup.appDelete).toBeFalsy(); expect(allUserGroup.folderCreate).toBeFalsy(); + expect(allUserGroup.folderUpdate).toBeFalsy(); + expect(allUserGroup.folderDelete).toBeFalsy(); }); it('authenticate if valid credentials', async () => { await request(app.getHttpServer()) @@ -227,10 +253,10 @@ describe('Authentication', () => { .expect(401); }); it('throw 401 if user is archived', async () => { - await createUser(app, { email: 'user@tooljet.io', status: 'archived' }); + const { orgUser } = await createUser(app, { email: 'user@tooljet.io', status: 'archived' }); await request(app.getHttpServer()) - .post('/api/authenticate') + .post(`/api/authenticate/${orgUser.organizationId}`) .send({ email: 'user@tooljet.io', password: 'password' }) .expect(401); @@ -244,6 +270,46 @@ describe('Authentication', () => { .set('Authorization', authHeaderForUser(adminUser)) .expect(401); }); + it('throw 401 if user is invited', async () => { + const { orgUser } = await createUser(app, { email: 'user@tooljet.io', status: 'invited' }); + + const response = await request(app.getHttpServer()) + .post(`/api/authenticate/${orgUser.organizationId}`) + .send({ email: 'user@tooljet.io', password: 'password' }) + .expect(401); + + const adminUser = await userRepository.findOneOrFail({ + email: 'admin@tooljet.io', + }); + await orgUserRepository.update({ userId: adminUser.id }, { status: 'invited' }); + + await request(app.getHttpServer()) + .get('/api/organizations/users') + .set('Authorization', authHeaderForUser(adminUser)) + .expect(401); + }); + it('login to new organization if user is archived', async () => { + const { orgUser } = await createUser(app, { email: 'user@tooljet.io', status: 'archived' }); + + const response = await request(app.getHttpServer()) + .post('/api/authenticate') + .send({ email: 'user@tooljet.io', password: 'password' }); + + expect(response.statusCode).toBe(201); + expect(response.body.organization_id).not.toBe(orgUser.organizationId); + expect(response.body.organization).toBe('Untitled workspace'); + }); + it('login to new organization if user is invited', async () => { + const { orgUser } = await createUser(app, { email: 'user@tooljet.io', status: 'invited' }); + + const response = await request(app.getHttpServer()) + .post('/api/authenticate') + .send({ email: 'user@tooljet.io', password: 'password' }); + + expect(response.statusCode).toBe(201); + expect(response.body.organization_id).not.toBe(orgUser.organizationId); + expect(response.body.organization).toBe('Untitled workspace'); + }); it('throw 401 if invalid credentials', async () => { await request(app.getHttpServer()) .post('/api/authenticate') @@ -322,6 +388,8 @@ describe('Authentication', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_delete', + 'folder_update', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -383,6 +451,8 @@ describe('Authentication', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_delete', + 'folder_update', ].sort() ); expect(app_group_permissions).toHaveLength(0); diff --git a/server/test/controllers/folders.e2e-spec.ts b/server/test/controllers/folders.e2e-spec.ts index 623ab826e4..af94321eb9 100644 --- a/server/test/controllers/folders.e2e-spec.ts +++ b/server/test/controllers/folders.e2e-spec.ts @@ -294,6 +294,114 @@ describe('folders controller', () => { }); }); + describe('PUT /api/folders/:id', () => { + it('should be able to update an existing folder if group is admin or has update permission in the same organization', async () => { + const adminUserData = await createUser(nestApp, { + email: 'admin@tooljet.io', + groups: ['all_users', 'admin'], + }); + const developerUserData = await createUser(nestApp, { + email: 'dev@tooljet.io', + groups: ['all_users', 'developer'], + organization: adminUserData.organization, + }); + + const viewerUserData = await createUser(nestApp, { + email: 'viewer@tooljet.io', + groups: ['viewer', 'all_users'], + organization: adminUserData.organization, + }); + + const developerGroup = await getManager().findOneOrFail(GroupPermission, { + where: { group: 'developer' }, + }); + + await getManager().update(GroupPermission, developerGroup.id, { + folderUpdate: true, + }); + + const folder = await getManager().save(Folder, { + name: 'Folder1', + organizationId: adminUserData.organization.id, + }); + + for (const userData of [adminUserData, developerUserData]) { + await request(nestApp.getHttpServer()) + .put(`/api/folders/${folder.id}`) + .set('Authorization', authHeaderForUser(userData.user)) + .send({ name: 'My folder' }) + .expect(200); + + const updatedFolder = await getManager().findOne(Folder, folder.id); + + expect(updatedFolder.name).toEqual('My folder'); + } + + await request(nestApp.getHttpServer()) + .put(`/api/folders/${folder.id}`) + .set('Authorization', authHeaderForUser(viewerUserData.user)) + .send({ name: 'My folder' }) + .expect(403); + }); + }); + + describe('DELETE /api/folders/:id', () => { + it('should be able to delete an existing folder if group is admin or has delete permission in the same organization', async () => { + const adminUserData = await createUser(nestApp, { + email: 'admin@tooljet.io', + groups: ['all_users', 'admin'], + }); + const developerUserData = await createUser(nestApp, { + email: 'dev@tooljet.io', + groups: ['all_users', 'developer'], + organization: adminUserData.organization, + }); + + const viewerUserData = await createUser(nestApp, { + email: 'viewer@tooljet.io', + groups: ['viewer', 'all_users'], + organization: adminUserData.organization, + }); + + const developerGroup = await getManager().findOneOrFail(GroupPermission, { + where: { group: 'developer' }, + }); + + await getManager().update(GroupPermission, developerGroup.id, { + folderDelete: true, + }); + + for (const userData of [adminUserData, developerUserData]) { + const folder = await getManager().save(Folder, { + name: 'Folder1', + organizationId: adminUserData.organization.id, + }); + + const preCount = await getManager().count(Folder); + + await request(nestApp.getHttpServer()) + .delete(`/api/folders/${folder.id}`) + .set('Authorization', authHeaderForUser(userData.user)) + .send() + .expect(200); + + const postCount = await getManager().count(Folder); + expect(postCount).toEqual(preCount - 1); + } + + const folder = await getManager().save(Folder, { + name: 'Folder1', + organizationId: adminUserData.organization.id, + }); + + await request(nestApp.getHttpServer()) + .delete(`/api/folders/${folder.id}`) + .set('Authorization', authHeaderForUser(viewerUserData.user)) + .send() + .expect(403); + }); + }); + afterAll(async () => { await nestApp.close(); }); diff --git a/server/test/controllers/oauth.e2e-spec.ts b/server/test/controllers/oauth.e2e-spec.ts index 5ea15f6438..ddf8185a62 100644 --- a/server/test/controllers/oauth.e2e-spec.ts +++ b/server/test/controllers/oauth.e2e-spec.ts @@ -165,6 +165,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -235,6 +237,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -295,6 +299,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -372,10 +378,93 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); }); + it('should return login info when the user exist but invited status', async () => { + const { orgUser } = await createUser(app, { + firstName: 'SSO', + lastName: 'userExist', + email: 'anotherUser1@tooljet.io', + groups: ['all_users'], + organization: current_organization, + status: 'invited', + }); + const googleVerifyMock = jest.spyOn(OAuth2Client.prototype, 'verifyIdToken'); + googleVerifyMock.mockImplementation(() => ({ + getPayload: () => ({ + sub: 'someSSOId', + email: 'anotherUser1@tooljet.io', + name: 'SSO User', + hd: 'tooljet.io', + }), + })); + + const response = await request(app.getHttpServer()) + .post('/api/oauth/sign-in/' + sso_configs.id) + .send({ token }); + + expect(googleVerifyMock).toHaveBeenCalledWith({ + idToken: token, + audience: sso_configs.configs.clientId, + }); + + expect(response.statusCode).toBe(201); + expect(Object.keys(response.body).sort()).toEqual( + [ + 'id', + 'email', + 'first_name', + 'last_name', + 'auth_token', + 'admin', + 'organization_id', + 'organization', + 'group_permissions', + 'app_group_permissions', + ].sort() + ); + + const { + email, + first_name, + last_name, + admin, + group_permissions, + app_group_permissions, + organization_id, + organization, + } = response.body; + + expect(email).toEqual('anotherUser1@tooljet.io'); + expect(first_name).toEqual('SSO'); + expect(last_name).toEqual('userExist'); + expect(admin).toBeFalsy(); + expect(organization_id).toBe(current_organization.id); + expect(organization).toBe(current_organization.name); + expect(group_permissions).toHaveLength(1); + expect(group_permissions[0].group).toEqual('all_users'); + expect(Object.keys(group_permissions[0]).sort()).toEqual( + [ + 'id', + 'organization_id', + 'group', + 'app_create', + 'app_delete', + 'updated_at', + 'created_at', + 'folder_create', + 'folder_delete', + 'folder_update', + ].sort() + ); + expect(app_group_permissions).toHaveLength(0); + await orgUser.reload(); + expect(orgUser.status).toEqual('active'); + }); }); describe('sign in via Git OAuth', () => { let sso_configs; @@ -538,6 +627,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -623,6 +714,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -705,6 +798,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -777,6 +872,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -869,6 +966,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -959,10 +1058,106 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); }); + it('should return login info when the user exist with invited status', async () => { + const { orgUser } = await createUser(app, { + firstName: 'SSO', + lastName: 'userExist', + email: 'anotherUser1@tooljet.io', + groups: ['all_users'], + organization: current_organization, + status: 'invited', + }); + + const gitAuthResponse = jest.fn(); + gitAuthResponse.mockImplementation(() => { + return { + json: () => { + return { + access_token: 'some-access-token', + scope: 'scope', + token_type: 'bearer', + }; + }, + }; + }); + const gitGetUserResponse = jest.fn(); + gitGetUserResponse.mockImplementation(() => { + return { + json: () => { + return { + name: 'SSO userExist', + email: 'anotherUser1@tooljet.io', + }; + }, + }; + }); + + mockedGot.mockImplementationOnce(gitAuthResponse); + mockedGot.mockImplementationOnce(gitGetUserResponse); + + const response = await request(app.getHttpServer()) + .post('/api/oauth/sign-in/' + sso_configs.id) + .send({ token }); + + expect(response.statusCode).toBe(201); + expect(Object.keys(response.body).sort()).toEqual( + [ + 'id', + 'email', + 'first_name', + 'last_name', + 'auth_token', + 'admin', + 'organization_id', + 'organization', + 'group_permissions', + 'app_group_permissions', + ].sort() + ); + + const { + email, + first_name, + last_name, + admin, + group_permissions, + app_group_permissions, + organization_id, + organization, + } = response.body; + + expect(email).toEqual('anotherUser1@tooljet.io'); + expect(first_name).toEqual('SSO'); + expect(last_name).toEqual('userExist'); + expect(admin).toBeFalsy(); + expect(organization_id).toBe(current_organization.id); + expect(organization).toBe(current_organization.name); + expect(group_permissions).toHaveLength(1); + expect(group_permissions[0].group).toEqual('all_users'); + expect(Object.keys(group_permissions[0]).sort()).toEqual( + [ + 'id', + 'organization_id', + 'group', + 'app_create', + 'app_delete', + 'updated_at', + 'created_at', + 'folder_create', + 'folder_delete', + 'folder_update', + ].sort() + ); + expect(app_group_permissions).toHaveLength(0); + await orgUser.reload(); + expect(orgUser.status).toEqual('active'); + }); }); }); @@ -1090,6 +1285,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1160,6 +1357,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1220,6 +1419,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1297,10 +1498,93 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); }); + it('should return login info when the user exist with invited status', async () => { + const { orgUser } = await createUser(app, { + firstName: 'SSO', + lastName: 'userExist', + email: 'anotherUser1@tooljet.io', + groups: ['all_users'], + organization: current_organization, + status: 'invited', + }); + const googleVerifyMock = jest.spyOn(OAuth2Client.prototype, 'verifyIdToken'); + googleVerifyMock.mockImplementation(() => ({ + getPayload: () => ({ + sub: 'someSSOId', + email: 'anotherUser1@tooljet.io', + name: 'SSO User', + hd: 'tooljet.io', + }), + })); + + const response = await request(app.getHttpServer()) + .post('/api/oauth/sign-in/' + sso_configs.id) + .send({ token }); + + expect(googleVerifyMock).toHaveBeenCalledWith({ + idToken: token, + audience: sso_configs.configs.clientId, + }); + + expect(response.statusCode).toBe(201); + expect(Object.keys(response.body).sort()).toEqual( + [ + 'id', + 'email', + 'first_name', + 'last_name', + 'auth_token', + 'admin', + 'organization_id', + 'organization', + 'group_permissions', + 'app_group_permissions', + ].sort() + ); + + const { + email, + first_name, + last_name, + admin, + group_permissions, + app_group_permissions, + organization_id, + organization, + } = response.body; + + expect(email).toEqual('anotherUser1@tooljet.io'); + expect(first_name).toEqual('SSO'); + expect(last_name).toEqual('userExist'); + expect(admin).toBeFalsy(); + expect(organization_id).toBe(current_organization.id); + expect(organization).toBe(current_organization.name); + expect(group_permissions).toHaveLength(1); + expect(group_permissions[0].group).toEqual('all_users'); + expect(Object.keys(group_permissions[0]).sort()).toEqual( + [ + 'id', + 'organization_id', + 'group', + 'app_create', + 'app_delete', + 'updated_at', + 'created_at', + 'folder_create', + 'folder_delete', + 'folder_update', + ].sort() + ); + expect(app_group_permissions).toHaveLength(0); + await orgUser.reload(); + expect(orgUser.status).toEqual('active'); + }); }); describe('sign in via Git OAuth', () => { let sso_configs; @@ -1463,6 +1747,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1548,6 +1834,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1630,6 +1918,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1702,6 +1992,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1794,6 +2086,8 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); @@ -1884,10 +2178,106 @@ describe('oauth controller', () => { 'updated_at', 'created_at', 'folder_create', + 'folder_update', + 'folder_delete', ].sort() ); expect(app_group_permissions).toHaveLength(0); }); + it('should return login info when the user exist with invited status', async () => { + const { orgUser } = await createUser(app, { + firstName: 'SSO', + lastName: 'userExist', + email: 'anotherUser1@tooljet.io', + groups: ['all_users'], + organization: current_organization, + status: 'invited', + }); + + const gitAuthResponse = jest.fn(); + gitAuthResponse.mockImplementation(() => { + return { + json: () => { + return { + access_token: 'some-access-token', + scope: 'scope', + token_type: 'bearer', + }; + }, + }; + }); + const gitGetUserResponse = jest.fn(); + gitGetUserResponse.mockImplementation(() => { + return { + json: () => { + return { + name: 'SSO userExist', + email: 'anotherUser1@tooljet.io', + }; + }, + }; + }); + + mockedGot.mockImplementationOnce(gitAuthResponse); + mockedGot.mockImplementationOnce(gitGetUserResponse); + + const response = await request(app.getHttpServer()) + .post('/api/oauth/sign-in/' + sso_configs.id) + .send({ token }); + + expect(response.statusCode).toBe(201); + expect(Object.keys(response.body).sort()).toEqual( + [ + 'id', + 'email', + 'first_name', + 'last_name', + 'auth_token', + 'admin', + 'organization_id', + 'organization', + 'group_permissions', + 'app_group_permissions', + ].sort() + ); + + const { + email, + first_name, + last_name, + admin, + group_permissions, + app_group_permissions, + organization_id, + organization, + } = response.body; + + expect(email).toEqual('anotherUser1@tooljet.io'); + expect(first_name).toEqual('SSO'); + expect(last_name).toEqual('userExist'); + expect(admin).toBeFalsy(); + expect(organization_id).toBe(current_organization.id); + expect(organization).toBe(current_organization.name); + expect(group_permissions).toHaveLength(1); + expect(group_permissions[0].group).toEqual('all_users'); + expect(Object.keys(group_permissions[0]).sort()).toEqual( + [ + 'id', + 'organization_id', + 'group', + 'app_create', + 'app_delete', + 'updated_at', + 'created_at', + 'folder_create', + 'folder_delete', + 'folder_update', + ].sort() + ); + expect(app_group_permissions).toHaveLength(0); + await orgUser.reload(); + expect(orgUser.status).toEqual('active'); + }); }); }); }); diff --git a/server/test/controllers/organization_users.e2e-spec.ts b/server/test/controllers/organization_users.e2e-spec.ts index 46ade25d99..66728fbb09 100644 --- a/server/test/controllers/organization_users.e2e-spec.ts +++ b/server/test/controllers/organization_users.e2e-spec.ts @@ -188,7 +188,7 @@ describe('organization users controller', () => { expect(viewerUserData.user.password).not.toBe('old-password'); }); - it('should allow unarchive if user is already archived', async () => { + it('should not allow unarchive if user status is not archived', async () => { const adminUserData = await createUser(app, { email: 'admin@tooljet.io', status: 'active', @@ -205,11 +205,34 @@ describe('organization users controller', () => { await request(app.getHttpServer()) .post(`/api/organization_users/${developerUserData.orgUser.id}/unarchive/`) .set('Authorization', authHeaderForUser(adminUserData.user)) - .expect(201); + .expect(400); await developerUserData.orgUser.reload(); expect(developerUserData.orgUser.status).toBe('active'); }); + + it('should not allow unarchive if user status is not archived', async () => { + const adminUserData = await createUser(app, { + email: 'admin@tooljet.io', + status: 'active', + groups: ['admin', 'all_users'], + }); + const organization = adminUserData.organization; + const developerUserData = await createUser(app, { + email: 'developer@tooljet.io', + status: 'invited', + groups: ['developer', 'all_users'], + organization, + }); + + await request(app.getHttpServer()) + .post(`/api/organization_users/${developerUserData.orgUser.id}/unarchive/`) + .set('Authorization', authHeaderForUser(adminUserData.user)) + .expect(400); + + await developerUserData.orgUser.reload(); + expect(developerUserData.orgUser.status).toBe('invited'); + }); }); afterAll(async () => { diff --git a/server/test/test.helper.ts b/server/test/test.helper.ts index a013474b2b..426d4f3a6e 100644 --- a/server/test/test.helper.ts +++ b/server/test/test.helper.ts @@ -321,6 +321,8 @@ export async function maybeCreateDefaultGroupPermissions(nestApp, organizationId appCreate: group == 'admin', appDelete: group == 'admin', folderCreate: group == 'admin', + folderUpdate: group == 'admin', + folderDelete: group == 'admin', }); await groupPermissionRepository.save(groupPermission); }
NameName
- + {this.humanizeifDefaultGroupName(permissionGroup.group)} {permissionGroup.group !== 'admin' && permissionGroup.group !== 'all_users' && ( - this.deleteGroup(permissionGroup.id)}>Delete + this.deleteGroup(permissionGroup.id)} data-cy="delete-link"> + Delete + )}