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=""

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!CAUTION]  
> If you modify the content in this section, you are likely to disrupt
the CI result for your PR.

<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Shivam kumar 2024-10-09 09:57:44 +05:30 committed by GitHub
parent 8463d02740
commit ac91339d54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 209 additions and 13 deletions

View File

@ -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) =>

View File

@ -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 <div>{props.children}</div>;
}
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(
<Provider store={store}>
<ReduxFormDecorator>
<RadioButtonControl {...radioButtonProps} />
</ReduxFormDecorator>
</Provider>,
);
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(
<Provider store={store}>
<ReduxFormDecorator>
<RadioButtonControl {...radioButtonProps} />
</ReduxFormDecorator>
</Provider>,
);
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(
<Provider store={store}>
<ReduxFormDecorator>
<RadioButtonControl {...radioButtonProps} />
</ReduxFormDecorator>
</Provider>,
);
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();
});
});

View File

@ -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<RadioButtonControlProps> {
getControlType(): ControlType {
return "RADIO_BUTTON";
}
render() {
return (
<Field
component={renderComponent}
name={this.props.configProperty}
props={{ ...this.props }}
type="radiobutton"
/>
);
}
}
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 (
<StyledRadioGroup
data-testid={props.input?.name}
defaultValue={defaultValue}
onChange={onChangeHandler}
>
{options.map((option) => {
return (
<Radio key={option.value} value={option.value}>
{option.label}
</Radio>
);
})}
</StyledRadioGroup>
);
}
export interface RadioButtonControlProps extends ControlProps {
options: SelectOptionProps[];
}
export default RadioButtonControl;

View File

@ -662,17 +662,6 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
>
{(!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 ? (
<AuthMessage
actionType={ActionType.DOCUMENTATION}
calloutType="info"
datasource={datasource}
description={googleSheetsInfoMessage}
pageId={pageId}
/>
) : null}
{/* This adds error banner for google sheets datasource if the datasource is unauthorised */}
{datasource &&
isGoogleSheetPlugin &&
@ -688,6 +677,17 @@ class DatasourceSaaSEditor extends JSONtoForm<Props, State> {
? 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 ? (
<AuthMessage
actionType={ActionType.DOCUMENTATION}
calloutType="info"
datasource={datasource}
description={googleSheetsInfoMessage}
pageId={pageId}
/>
) : null}
</>
)}
{viewMode &&

View File

@ -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 <RadioButtonControl {...controlProps} />;
},
});
}
}

View File

@ -18,4 +18,5 @@ export default {
PROJECTION: "PROJECTION",
FORM_TEMPLATE: "FORM_TEMPLATE",
MULTIPLE_FILE_PICKER: "MULTIPLE_FILE_PICKER",
RADIO_BUTTON: "RADIO_BUTTON",
};

View File

@ -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",