From ac91339d5448f396a462356d998d9fcab836cd10 Mon Sep 17 00:00:00 2001 From: Shivam kumar Date: Wed, 9 Oct 2024 09:57:44 +0530 Subject: [PATCH] chore:remove space b/w form and CTA onboarding page (#35985) ## Description Following are the improvements made in this PR: - Remove the unnecessary space b/w form and CTA in Gsheet onboarding step - Made one new RadioButtonControl in form control and replaced the current dropdown by radio buttons. - Move the callout to after the permissions | scope property. - Limit the width of the white section Fixes #30523 output screenshot: ![Screenshot from 2024-09-20 15-14-59](https://github.com/user-attachments/assets/61b397fb-8735-4b36-8036-a781ab3bd936) Desired design: ![image](https://github.com/user-attachments/assets/df65fab5-c543-4af8-9bb5-f72d8cb4d004) > > _Please also include relevant motivation and context. List any dependencies that are required for this change. Add links to Notion, Figma or any other documents that might be relevant to the PR._ *Fixes #`35950` _or_ Fixes `Issue URL` > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /ok-to-test tags="" ### :mag: Cypress test results > [!CAUTION] > If you modify the content in this section, you are likely to disrupt the CI result for your PR. ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Introduced a new `RadioButtonControl` component for improved form control options. - Enhanced the `FormControlRegistry` to support radio button controls. - Updated the Google Sheets plugin to use radio buttons for permission settings. - **UI Changes** - Corrected styling syntax in the `FormContainer` for proper rendering. - Reorganized the display order of information banners in the `DatasourceForm` for better clarity. - **Tests** - Added a comprehensive suite of unit tests for the `RadioButtonControl` component to ensure proper functionality and user interaction. --- .../cypress/support/Pages/DataSources.ts | 2 +- .../formControls/RadioButtonControl.test.tsx | 118 ++++++++++++++++++ .../formControls/RadioButtonControl.tsx | 70 +++++++++++ .../Editor/SaaSEditor/DatasourceForm.tsx | 22 ++-- .../utils/formControl/FormControlRegistry.tsx | 7 ++ .../src/utils/formControl/formControlTypes.ts | 1 + .../src/main/resources/form.json | 2 +- 7 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 app/client/src/components/formControls/RadioButtonControl.test.tsx create mode 100644 app/client/src/components/formControls/RadioButtonControl.tsx diff --git a/app/client/cypress/support/Pages/DataSources.ts b/app/client/cypress/support/Pages/DataSources.ts index b41cbd05b3..4770826380 100644 --- a/app/client/cypress/support/Pages/DataSources.ts +++ b/app/client/cypress/support/Pages/DataSources.ts @@ -194,7 +194,7 @@ export class DataSources { _globalSearchInput = ".t--global-search-input"; _gsScopeDropdown = "[data-testid^='datasourceStorages.'][data-testid$='.datasourceConfiguration.authentication.scopeString']"; - _gsScopeOptions = ".ads-v2-select__dropdown .rc-select-item-option"; + _gsScopeOptions = ".ads-v2-radio-group"; _queryTimeout = "//input[@name='actionConfiguration.timeoutInMillisecond']"; _getStructureReq = "/api/v1/datasources/*/structure?ignoreCache=true"; _editDatasourceFromActiveTab = (dsName: string) => diff --git a/app/client/src/components/formControls/RadioButtonControl.test.tsx b/app/client/src/components/formControls/RadioButtonControl.test.tsx new file mode 100644 index 0000000000..03077c379b --- /dev/null +++ b/app/client/src/components/formControls/RadioButtonControl.test.tsx @@ -0,0 +1,118 @@ +import React from "react"; +import RadioButtonControl from "./RadioButtonControl"; +import { render, screen } from "test/testUtils"; +import { Provider } from "react-redux"; +import { reduxForm } from "redux-form"; +import configureStore from "redux-mock-store"; +import "@testing-library/jest-dom"; + +const mockStore = configureStore([]); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function TestForm(props: any) { + return
{props.children}
; +} + +const ReduxFormDecorator = reduxForm({ + form: "TestForm", +})(TestForm); + +const mockOptions = [ + { label: "Option 1", value: "option1", children: "Option 1" }, + { label: "Option 2", value: "option2", children: "Option 2" }, + { label: "Option 3", value: "option3", children: "Option 3" }, +]; + +let radioButtonProps = { + options: mockOptions, + configProperty: "actionConfiguration.testPath", + controlType: "PROJECTION", + label: "Columns", + id: "column", + formName: "", + isValid: true, + initialValue: "option1", +}; + +describe("RadioButtonControl", () => { + const mockStoreInstance = mockStore(); + let store: typeof mockStoreInstance; + + beforeEach(() => { + store = mockStore(); + }); + it("should render RadioButtonControl and options properly", async () => { + render( + + + + + , + ); + const radioButton = (await screen.findByTestId( + "actionConfiguration.testPath", + )) as HTMLElement; + + expect(radioButton).toBeInTheDocument(); + + const options = screen.getAllByRole("radio"); + + expect(options).toHaveLength(3); + }); + + it("should show the default selected option", async () => { + radioButtonProps = { + ...radioButtonProps, + }; + + render( + + + + + , + ); + const radioButton = (await screen.findByTestId( + "actionConfiguration.testPath", + )) as HTMLElement; + + expect(radioButton).toBeInTheDocument(); + + const options = screen.getAllByRole("radio"); + + expect(options[0]).toBeChecked(); + expect(options[1]).not.toBeChecked(); + expect(options[2]).not.toBeChecked(); + }); + + it("should select the option when clicked", async () => { + radioButtonProps = { + ...radioButtonProps, + }; + + render( + + + + + , + ); + const radioButton = (await screen.findByTestId( + "actionConfiguration.testPath", + )) as HTMLElement; + + expect(radioButton).toBeInTheDocument(); + + const options = screen.getAllByRole("radio"); + + expect(options[0]).toBeChecked(); + expect(options[1]).not.toBeChecked(); + expect(options[2]).not.toBeChecked(); + + options[1].click(); + + expect(options[0]).not.toBeChecked(); + expect(options[1]).toBeChecked(); + expect(options[2]).not.toBeChecked(); + }); +}); diff --git a/app/client/src/components/formControls/RadioButtonControl.tsx b/app/client/src/components/formControls/RadioButtonControl.tsx new file mode 100644 index 0000000000..ecee054f58 --- /dev/null +++ b/app/client/src/components/formControls/RadioButtonControl.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import type { ControlProps } from "./BaseControl"; +import BaseControl from "./BaseControl"; +import type { ControlType } from "constants/PropertyControlConstants"; +import type { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form"; +import { Field } from "redux-form"; +import { Radio, RadioGroup, type SelectOptionProps } from "@appsmith/ads"; +import styled from "styled-components"; + +class RadioButtonControl extends BaseControl { + getControlType(): ControlType { + return "RADIO_BUTTON"; + } + render() { + return ( + + ); + } +} + +type renderComponentProps = RadioButtonControlProps & { + input?: WrappedFieldInputProps; + meta?: WrappedFieldMetaProps; + options?: Array<{ label: string; value: string }>; +}; + +const StyledRadioGroup = styled(RadioGroup)({ + display: "flex", + flexDirection: "column", + gap: "16px", + marginTop: "16px", +}); + +function renderComponent(props: renderComponentProps) { + const onChangeHandler = (value: string) => { + if (typeof props.input?.onChange === "function") { + props.input.onChange(value); + } + }; + + const options = props.options || []; + const defaultValue = props.initialValue as string; + + return ( + + {options.map((option) => { + return ( + + {option.label} + + ); + })} + + ); +} + +export interface RadioButtonControlProps extends ControlProps { + options: SelectOptionProps[]; +} + +export default RadioButtonControl; diff --git a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx index 414c920f5a..94e34ba1c0 100644 --- a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx +++ b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx @@ -662,17 +662,6 @@ class DatasourceSaaSEditor extends JSONtoForm { > {(!viewMode || createFlow || isInsideReconnectModal) && ( <> - {/* This adds information banner when creating google sheets datasource, - this info banner explains why appsmith requires permissions from users google account */} - {datasource && isGoogleSheetPlugin && createFlow ? ( - - ) : null} {/* This adds error banner for google sheets datasource if the datasource is unauthorised */} {datasource && isGoogleSheetPlugin && @@ -688,6 +677,17 @@ class DatasourceSaaSEditor extends JSONtoForm { ? map(sections, this.renderMainSection) : null} {""} + {/* This adds information banner when creating google sheets datasource, + this info banner explains why appsmith requires permissions from users google account */} + {datasource && isGoogleSheetPlugin && createFlow ? ( + + ) : null} )} {viewMode && diff --git a/app/client/src/utils/formControl/FormControlRegistry.tsx b/app/client/src/utils/formControl/FormControlRegistry.tsx index 90fe162363..0779695fbd 100644 --- a/app/client/src/utils/formControl/FormControlRegistry.tsx +++ b/app/client/src/utils/formControl/FormControlRegistry.tsx @@ -35,6 +35,8 @@ import FormTemplateControl from "components/formControls/FormTemplateControl"; import type { FormTemplateControlProps } from "components/formControls/FormTemplateControl"; import MultiFilePickerControl from "components/formControls/MultiFilePickerControl"; import type { MultipleFilePickerControlProps } from "components/formControls/MultiFilePickerControl"; +import type { RadioButtonControlProps } from "components/formControls/RadioButtonControl"; +import RadioButtonControl from "components/formControls/RadioButtonControl"; /** * NOTE: If you are adding a component that uses FormControl @@ -183,6 +185,11 @@ class FormControlRegistry { }, }, ); + FormControlFactory.registerControlBuilder(formControlTypes.RADIO_BUTTON, { + buildPropertyControl(controlProps: RadioButtonControlProps): JSX.Element { + return ; + }, + }); } } diff --git a/app/client/src/utils/formControl/formControlTypes.ts b/app/client/src/utils/formControl/formControlTypes.ts index 053f0d6f85..86242a50bd 100644 --- a/app/client/src/utils/formControl/formControlTypes.ts +++ b/app/client/src/utils/formControl/formControlTypes.ts @@ -18,4 +18,5 @@ export default { PROJECTION: "PROJECTION", FORM_TEMPLATE: "FORM_TEMPLATE", MULTIPLE_FILE_PICKER: "MULTIPLE_FILE_PICKER", + RADIO_BUTTON: "RADIO_BUTTON", }; diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/resources/form.json index d5f7709c31..8e9fe67783 100644 --- a/app/server/appsmith-plugins/googleSheetsPlugin/src/main/resources/form.json +++ b/app/server/appsmith-plugins/googleSheetsPlugin/src/main/resources/form.json @@ -33,7 +33,7 @@ { "label": "Permissions | Scope", "configProperty": "datasourceConfiguration.authentication.scopeString", - "controlType": "DROP_DOWN", + "controlType": "RADIO_BUTTON", "options": [ { "label": "Read / Write / Delete | Selected google sheets",