diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Autocomplete_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Autocomplete_spec.js index 1239a55293..f5ccddde1f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Autocomplete_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Autocomplete_spec.js @@ -110,6 +110,9 @@ describe("Autocomplete using slash command and mustache tests", function() { // validates autocomplete binding on entering {{}} in text field cy.get(`${dynamicInputLocators.hints} li`) .eq(1) + .should("have.text", "Button1.recaptchaToken"); + cy.get(`${dynamicInputLocators.hints} li`) + .eq(2) .should("have.text", "Button1.text"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js index 961dc0b0cf..a14ec67435 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js @@ -149,6 +149,13 @@ describe("Button Widget Functionality", function() { cy.get(publishPage.buttonWidget).should("be.visible"); }); + it("Button-Check recaptcha type can be selected", function() { + cy.selectDropdownValue(commonlocators.recaptchaVersion, "reCAPTCHA v2"); + cy.get(commonlocators.recaptchaVersion) + .last() + .should("have.text", "reCAPTCHA v2"); + }); + it("Button-Copy Verification", function() { //Copy button and verify all properties cy.copyWidget("buttonwidget", widgetsPage.buttonWidget); diff --git a/app/client/cypress/locators/commonlocators.json b/app/client/cypress/locators/commonlocators.json index e360cadbb9..d419a25eb4 100644 --- a/app/client/cypress/locators/commonlocators.json +++ b/app/client/cypress/locators/commonlocators.json @@ -110,6 +110,7 @@ "filePickerUploadButton": ".uppy-StatusBar-actionBtn--upload", "filePickerOnFilesSelected": ".t--property-control-onfilesselected", "dataType": ".t--property-control-datatype .bp3-popover-target", + "recaptchaVersion": ".t--property-control-googlerecaptchaversion .bp3-popover-target", "evaluateMsg": ".t--evaluatedPopup-error", "globalSearchModal": ".t--global-search-modal", "globalSearchInput": ".t--global-search-input", diff --git a/app/client/src/components/constants.ts b/app/client/src/components/constants.ts index 5878c30216..a692ea72e9 100644 --- a/app/client/src/components/constants.ts +++ b/app/client/src/components/constants.ts @@ -66,6 +66,12 @@ export enum ButtonVariantTypes { } export type ButtonVariant = keyof typeof ButtonVariantTypes; +export enum RecaptchaTypes { + V3 = "V3", + V2 = "V2", +} +export type RecaptchaType = keyof typeof RecaptchaTypes; + export enum CheckboxGroupAlignmentTypes { START = "flex-start", END = "flex-end", diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index bb773077df..4cf04ed457 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -69,7 +69,7 @@ export const layoutConfigurations: LayoutConfigurations = { FLUID: { minWidth: -1, maxWidth: -1 }, }; -export const LATEST_PAGE_VERSION = 48; +export const LATEST_PAGE_VERSION = 49; export const GridDefaults = { DEFAULT_CELL_SIZE: 1, diff --git a/app/client/src/utils/DSLMigrations.ts b/app/client/src/utils/DSLMigrations.ts index 3d4c99d45a..0507dfcf90 100644 --- a/app/client/src/utils/DSLMigrations.ts +++ b/app/client/src/utils/DSLMigrations.ts @@ -44,6 +44,7 @@ import { migrateResizableModalWidgetProperties } from "./migrations/ModalWidget" import { migrateCheckboxGroupWidgetInlineProperty } from "./migrations/CheckboxGroupWidget"; import { migrateMapWidgetIsClickedMarkerCentered } from "./migrations/MapWidget"; import { DSLWidget } from "widgets/constants"; +import { migrateRecaptchaType } from "./migrations/ButtonWidgetMigrations"; /** * adds logBlackList key for all list widget children @@ -996,6 +997,11 @@ export const transformDSL = ( if (currentDSL.version === 47) { currentDSL = migrateTableWidgetNumericColumnName(currentDSL); + currentDSL.version = 48; + } + + if (currentDSL.version === 48) { + currentDSL = migrateRecaptchaType(currentDSL); currentDSL.version = LATEST_PAGE_VERSION; } diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index dd6671a329..17f7f6227f 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -203,6 +203,7 @@ export const entityDefinitions: Record = { isVisible: isVisible, text: "string", isDisabled: "bool", + recaptchaToken: "string", }, DATE_PICKER_WIDGET: { "!doc": @@ -283,6 +284,7 @@ export const entityDefinitions: Record = { isVisible: isVisible, text: "string", isDisabled: "bool", + recaptchaToken: "string", }, MAP_WIDGET: { isVisible: isVisible, diff --git a/app/client/src/utils/migrations/ButtonWidgetMigrations.ts b/app/client/src/utils/migrations/ButtonWidgetMigrations.ts new file mode 100644 index 0000000000..2ac18ebe9f --- /dev/null +++ b/app/client/src/utils/migrations/ButtonWidgetMigrations.ts @@ -0,0 +1,21 @@ +import { WidgetProps } from "widgets/BaseWidget"; +import { DSLWidget } from "widgets/constants"; +import { RecaptchaTypes } from "components/constants"; + +export const migrateRecaptchaType = (currentDSL: DSLWidget): DSLWidget => { + currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { + if (child.type === "BUTTON_WIDGET" || child.type === "FORM_BUTTON_WIDGET") { + const recaptchaV2 = child.recaptchaV2; + if (recaptchaV2) { + child.recaptchaType = RecaptchaTypes.V2; + } else { + child.recaptchaType = RecaptchaTypes.V3; + } + delete child.recaptchaV2; + } else if (child.children && child.children.length > 0) { + child = migrateRecaptchaType(child); + } + return child; + }); + return currentDSL; +}; diff --git a/app/client/src/widgets/ButtonWidget/component/index.tsx b/app/client/src/widgets/ButtonWidget/component/index.tsx index ff64ec55e6..7876ae03b1 100644 --- a/app/client/src/widgets/ButtonWidget/component/index.tsx +++ b/app/client/src/widgets/ButtonWidget/component/index.tsx @@ -32,6 +32,8 @@ import { ButtonBorderRadiusTypes, ButtonVariant, ButtonVariantTypes, + RecaptchaType, + RecaptchaTypes, ButtonPlacement, } from "components/constants"; import { @@ -268,7 +270,7 @@ interface RecaptchaProps { googleRecaptchaKey?: string; clickWithRecaptcha: (token: string) => void; handleRecaptchaV2Loading?: (isLoading: boolean) => void; - recaptchaV2?: boolean; + recaptchaType?: RecaptchaType; } interface ButtonComponentProps extends ComponentProps { @@ -294,7 +296,7 @@ function RecaptchaV2Component( props: { children: any; onClick?: (event: React.MouseEvent) => void; - recaptchaV2?: boolean; + recaptchaType?: RecaptchaType; handleError: (event: React.MouseEvent, error: string) => void; } & RecaptchaProps, ) { @@ -343,7 +345,7 @@ function RecaptchaV3Component( props: { children: any; onClick?: (event: React.MouseEvent) => void; - recaptchaV2?: boolean; + recaptchaType?: RecaptchaType; handleError: (event: React.MouseEvent, error: string) => void; } & RecaptchaProps, ) { @@ -410,7 +412,7 @@ function BtnWrapper( }); props.onClick && props.onClick(event); }; - if (props.recaptchaV2) { + if (props.recaptchaType === RecaptchaTypes.V2) { return ; } else { return ; @@ -426,7 +428,7 @@ function ButtonComponent(props: ButtonComponentProps & RecaptchaProps) { googleRecaptchaKey={props.googleRecaptchaKey} handleRecaptchaV2Loading={props.handleRecaptchaV2Loading} onClick={props.onClick} - recaptchaV2={props.recaptchaV2} + recaptchaType={props.recaptchaType} > { validation: { type: ValidationTypes.TEXT }, }, { - propertyName: "recaptchaV2", + propertyName: "recaptchaType", label: "Google reCAPTCHA Version", controlType: "DROP_DOWN", helpText: "Select reCAPTCHA version", options: [ { label: "reCAPTCHA v3", - value: false, + value: RecaptchaTypes.V3, }, { label: "reCAPTCHA v2", - value: true, + value: RecaptchaTypes.V2, }, ], isBindProperty: true, isTriggerProperty: false, - validation: { type: ValidationTypes.BOOLEAN }, + validation: { + type: ValidationTypes.TEXT, + params: { + allowedValues: [RecaptchaTypes.V3, RecaptchaTypes.V2], + default: RecaptchaTypes.V3, + }, + }, }, { propertyName: "isVisible", @@ -378,7 +386,7 @@ class ButtonWidget extends BaseWidget { key={this.props.widgetId} onClick={!this.props.isDisabled ? this.onButtonClickBound : undefined} placement={this.props.placement} - recaptchaV2={this.props.recaptchaV2} + recaptchaType={this.props.recaptchaType} text={this.props.text} tooltip={this.props.tooltip} type={this.props.buttonType || ButtonType.BUTTON} @@ -398,7 +406,7 @@ export interface ButtonWidgetProps extends WidgetProps { onClick?: string; isDisabled?: boolean; isVisible?: boolean; - recaptchaV2?: boolean; + recaptchaType?: RecaptchaType; buttonType?: ButtonType; googleRecaptchaKey?: string; buttonVariant?: ButtonVariant; diff --git a/app/client/src/widgets/FormButtonWidget/index.ts b/app/client/src/widgets/FormButtonWidget/index.ts index ddcd80d2ff..170ff3ba1b 100644 --- a/app/client/src/widgets/FormButtonWidget/index.ts +++ b/app/client/src/widgets/FormButtonWidget/index.ts @@ -1,6 +1,7 @@ import Widget from "./widget"; import IconSVG from "./icon.svg"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; +import { RecaptchaTypes } from "components/constants"; export const CONFIG = { type: Widget.getWidgetType(), @@ -14,7 +15,7 @@ export const CONFIG = { widgetName: "FormButton", text: "Submit", isDefaultClickDisabled: true, - recaptchaV2: false, + recaptchaType: RecaptchaTypes.V3, version: 1, animateLoading: true, }, diff --git a/app/client/src/widgets/FormButtonWidget/widget/index.tsx b/app/client/src/widgets/FormButtonWidget/widget/index.tsx index d316c37a72..e7dccf5cb5 100644 --- a/app/client/src/widgets/FormButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/FormButtonWidget/widget/index.tsx @@ -12,6 +12,8 @@ import { ButtonBorderRadius, ButtonBoxShadow, ButtonVariant, + RecaptchaType, + RecaptchaTypes, } from "components/constants"; import { IconName } from "@blueprintjs/icons"; import { Alignment } from "@blueprintjs/core"; @@ -100,14 +102,29 @@ class FormButtonWidget extends ButtonWidget { validation: { type: ValidationTypes.TEXT }, }, { - propertyName: "recaptchaV2", - label: "Google reCAPTCHA v2", - controlType: "SWITCH", - helpText: "Use reCAPTCHA v2", - isJSConvertible: true, + propertyName: "recaptchaType", + label: "Google reCAPTCHA Version", + controlType: "DROP_DOWN", + helpText: "Select reCAPTCHA version", + options: [ + { + label: "reCAPTCHA v3", + value: RecaptchaTypes.V3, + }, + { + label: "reCAPTCHA v2", + value: RecaptchaTypes.V2, + }, + ], isBindProperty: true, isTriggerProperty: false, - validation: { type: ValidationTypes.BOOLEAN }, + validation: { + type: ValidationTypes.TEXT, + params: { + allowedValues: [RecaptchaTypes.V3, RecaptchaTypes.V2], + default: RecaptchaTypes.V3, + }, + }, }, ], }); @@ -188,7 +205,7 @@ export interface FormButtonWidgetProps extends WidgetProps { onReset?: () => void; disabledWhenInvalid?: boolean; googleRecaptchaKey?: string; - recaptchaV2?: boolean; + recaptchaType: RecaptchaType; buttonVariant?: ButtonVariant; buttonColor?: string; borderRadius?: ButtonBorderRadius; diff --git a/app/client/src/widgets/FormWidget/index.ts b/app/client/src/widgets/FormWidget/index.ts index a9acd4a883..fa4df4d02d 100644 --- a/app/client/src/widgets/FormWidget/index.ts +++ b/app/client/src/widgets/FormWidget/index.ts @@ -1,7 +1,7 @@ import Widget from "./widget"; import IconSVG from "./icon.svg"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; -import { ButtonVariantTypes } from "components/constants"; +import { ButtonVariantTypes, RecaptchaTypes } from "components/constants"; import { Colors } from "constants/Colors"; export const CONFIG = { @@ -59,7 +59,7 @@ export const CONFIG = { buttonColor: Colors.GREEN, disabledWhenInvalid: true, resetFormOnClick: true, - recaptchaV2: false, + recaptchaType: RecaptchaTypes.V3, version: 1, }, }, @@ -79,7 +79,7 @@ export const CONFIG = { buttonColor: Colors.GREEN, disabledWhenInvalid: false, resetFormOnClick: true, - recaptchaV2: false, + recaptchaType: RecaptchaTypes.V3, version: 1, }, },