Skip to content

Commit

Permalink
feat: action redesign, UQI upgrade S3 plugin config to dual zone form…
Browse files Browse the repository at this point in the history
…at & sorting field responsiveness (#36090)

## Description
Upgrade S3 plugin config to new format using SECTION_V2,
SINGLE_COLUMN_ZONE, and DOUBLE_COLUMN_ZONE.

Fixes #35484

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/10720588484>
> Commit: c66dce6
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10720588484&attempt=3"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Thu, 05 Sep 2024 15:04:31 UTC
<!-- end of auto-generated comment: Cypress test results  -->


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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **New Features**
- Enhanced sorting control with improved performance and
maintainability.
- New configuration option for specifying the expiration duration of
signed URLs in the Amazon S3 plugin.
- Updated UI layout for various actions (upload, delete, read) in the
Amazon S3 plugin to improve organization and user experience.
- Clarified labeling and structure in the Amazon S3 plugin for better
user interaction.

- **Bug Fixes**
- Streamlined logic for adding and deleting sorting fields in the
sorting component.

- **Documentation**
- Updated control types and structure in the Amazon S3 plugin
configuration for clarity and usability.

- **Style**
- Improved responsiveness of the sorting control layout and Amazon S3
plugin UI.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
alex-golovanov authored Sep 9, 2024
1 parent 1cf452f commit 205ba07
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 240 deletions.
219 changes: 98 additions & 121 deletions app/client/src/components/formControls/SortingControl.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, { useEffect, useRef } from "react";
import React, { useEffect, useMemo, useRef } from "react";
import { useSelector } from "react-redux";
import FormControl from "pages/Editor/FormControl";
import { Classes } from "@appsmith/ads-old";
import styled from "styled-components";
import { FieldArray, getFormValues } from "redux-form";
import {
FieldArray,
getFormValues,
type WrappedFieldArrayProps,
} from "redux-form";
import type { ControlProps } from "./BaseControl";
import { getBindingOrConfigPathsForSortingControl } from "entities/Action/actionProperties";
import { SortingSubComponent } from "./utils";
Expand All @@ -26,9 +29,6 @@ const columnFieldConfig: any = {
initialValue: "",
inputType: "TEXT",
placeholderText: "Column name",
customStyles: {
// width: "280px",
},
};

// Form config for the order field
Expand All @@ -52,174 +52,151 @@ const orderFieldConfig: any = {
],
};

// main container for the fsorting component
const SortingContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
const SortingContainer = styled.div<{ isBreakpointSmall: boolean }>`
display: grid;
grid-template-columns: ${({ isBreakpointSmall }) =>
isBreakpointSmall ? "1fr 50px" : "1fr 50px"};
gap: 5px;
align-items: center;
`;

// container for the two sorting dropdown
const SortingDropdownContainer = styled.div<{ size: string }>`
display: flex;
flex-direction: row;
width: min-content;
justify-content: space-between;
margin-bottom: 5px;
const SortingFields = styled.div<{ isBreakpointSmall: boolean }>`
display: grid;
grid-template-columns: ${({ isBreakpointSmall }) =>
isBreakpointSmall ? "1fr" : "1fr 180px"};
grid-template-rows: ${({ isBreakpointSmall }) =>
isBreakpointSmall ? "1fr 1fr" : "1fr"};
gap: 5px;
align-items: center;
> div {
width: 250px;
}
${(props) =>
props.size === "small" &&
`
// Hide the dropdown labels to decrease the width
// The design system component has inline style hence the !important
.t--form-control-DROP_DOWN .${Classes.TEXT} {
display: none !important;
}
// Show the icons hidden initially
.t--form-control-DROP_DOWN .remixicon-icon {
display: initial;
}
`}
`;

const ButtonWrapper = styled.div`
display: flex;
flex-direction: row;
`;
// container for the column dropdown section
const ColumnDropdownContainer = styled.div``;

// Component for the icons
const CenteredButton = styled(Button)``;
export type SortingControlProps = ControlProps;

// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function SortingComponent(props: any) {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const formValues: any = useSelector((state) =>
export type SortingComponentProps = WrappedFieldArrayProps &
Pick<SortingControlProps, "configProperty" | "formName">;

function SortingComponent(props: SortingComponentProps) {
const { configProperty, fields, formName } = props;

const formValues = useSelector((state) =>
getFormValues(props.formName)(state),
);

const onDeletePressed = (index: number) => {
props.fields.remove(index);
fields.remove(index);
};

const targetRef = useRef<HTMLDivElement>(null);
const size = useResponsiveBreakpoints(targetRef, [{ small: 450 }]);
const isBreakpointSmall = size === "small";

useEffect(() => {
// this path represents the path to the sortBy object, wherever the location is in the actionConfiguration object
let sortingObjectPath;
// if the path ends with .data which we expect it to.
if (props.configProperty.endsWith(".data")) {
if (configProperty.endsWith(".data")) {
// we remove the .data and get the path of the sort object
// NOTE: 5 is used because (.data) = 5
sortingObjectPath = props.configProperty.substring(
const sortingObjectPath = configProperty.substring(
0,
props.configProperty.length - 5,
configProperty.length - 5,
);
}
// sortDataValue is the path to the value (.data included) itself in the sort object
const sortDataValue = get(formValues, props.configProperty);
// sort object value is the path to the sort object itself.
const sortObjectValue = get(formValues, sortingObjectPath);

// The reason we are making this check is to prevent new fields from being pushed when the form control is visited
// for some reason the fields object is initially undefined in first render, before being initialized with the correct values after.
// so we check to see if the sortObjectValue exist first (if the value has been initalized).
if (!sortObjectValue) {
return;
}

// then we check if the redux fields have any items in it,
// and we also check if the value exists in the redux state as an array and if that value has no items in it.
// if they are both empty we want to push a new field.
// We also want to check if the value is undefined, this means that the sort data value is non existent, if it is, we want to push a new field.
if (
(props.fields.length < 1 &&
isArray(sortDataValue) &&
sortDataValue.length < 1) ||
(props.fields.length < 1 && !sortDataValue)
) {
props.fields.push({
column: "",
order: OrderDropDownValues.ASCENDING,
});
} else {
onDeletePressed(props.index);
// sortDataValue is the path to the value (.data included) itself in the sort object
const sortDataValue = get(formValues, configProperty);
// sort object value is the path to the sort object itself.
const sortObjectValue = get(formValues, sortingObjectPath);

// The reason we are making this check is to prevent new fields from being pushed when the form control is visited
// for some reason the fields object is initially undefined in first render, before being initialized with the correct values after.
// so we check to see if the sortObjectValue exist first (if the value has been initalized).
if (sortObjectValue) {
// then we check if the redux fields have any items in it,
// and we also check if the value exists in the redux state as an array and if that value has no items in it.
// if they are both empty we want to push a new field.
// We also want to check if the value is undefined, this means that the sort data value is non existent, if it is, we want to push a new field.
if (
(fields.length < 1 &&
isArray(sortDataValue) &&
sortDataValue.length < 1) ||
(fields.length < 1 && !sortDataValue)
) {
fields.push({
column: "",
order: OrderDropDownValues.ASCENDING,
});
}
}
}
}, [props.fields.length]);
}, [fields.length]);

return (
<SortingContainer className={`t--${props?.configProperty}`} ref={targetRef}>
{props.fields &&
props.fields.length > 0 &&
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
props.fields.map((field: any, index: number) => {
<SortingContainer
className={`t--${props?.configProperty}`}
isBreakpointSmall={isBreakpointSmall}
ref={targetRef}
>
{fields &&
fields.length > 0 &&
fields.map((field, index: number) => {
const columnPath = getBindingOrConfigPathsForSortingControl(
SortingSubComponent.Column,
field,
undefined,
);
const OrderPath = getBindingOrConfigPathsForSortingControl(

const orderPath = getBindingOrConfigPathsForSortingControl(
SortingSubComponent.Order,
field,
undefined,
);

return (
<SortingDropdownContainer key={index} size={size}>
<ColumnDropdownContainer>
<React.Fragment key={index}>
<SortingFields isBreakpointSmall={isBreakpointSmall}>
<FormControl
config={{
...columnFieldConfig,
customStyles: {
width: "250px",
width: "100%",
},
configProperty: `${columnPath}`,
configProperty: columnPath,
nestedFormControl: true,
}}
formName={props.formName}
formName={formName}
/>
</ColumnDropdownContainer>
<FormControl
config={{
...orderFieldConfig,
configProperty: `${OrderPath}`,
nestedFormControl: true,
customStyles: {
width: isBreakpointSmall ? "65px" : "250px",
},
optionWidth: isBreakpointSmall ? "250px" : undefined,
}}
formName={props.formName}
/>
{/* Component to render the delete icon */}
<CenteredButton
<FormControl
config={{
...orderFieldConfig,
customStyles: {
maxWidth: "180px",
},
configProperty: orderPath,
nestedFormControl: true,
}}
formName={formName}
/>
</SortingFields>
<Button
data-testid={`t--sorting-delete-[${index}]`}
isIconButton
kind="tertiary"
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onClick={() => {
onDeletePressed(index);
}}
size="md"
startIcon="close-line"
value={index}
/>
</SortingDropdownContainer>
</React.Fragment>
);
})}
<ButtonWrapper>
<Button
data-testid={`t--sorting-add-field`}
kind="tertiary"
onClick={() =>
props.fields.push({
fields.push({
column: "",
order: OrderDropDownValues.ASCENDING,
})
Expand All @@ -240,18 +217,18 @@ export default function SortingControl(props: SortingControlProps) {
formName, // Name of the form, used by redux-form lib to store the data in redux store
} = props;

const fieldArrayProps = useMemo(
() => ({ configProperty, formName }),
[configProperty, formName],
);

return (
<FieldArray
component={SortingComponent}
key={`${configProperty}`}
name={`${configProperty}`}
props={{
configProperty,
formName,
}}
key={configProperty}
name={configProperty}
props={fieldArrayProps}
rerenderOnEveryChange={false}
/>
);
}

export type SortingControlProps = ControlProps;
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{
"identifier": "UPLOAD_FILE_FROM_BODY",
"controlType": "SECTION",
"controlType": "SECTION_V2",
"conditionals": {
"show": "{{actionConfiguration.formData.command.data === 'UPLOAD_FILE_FROM_BODY'}}"
},
"children": [
{
"controlType": "SECTION",
"label": "Select bucket to query",
"controlType": "DOUBLE_COLUMN_ZONE",
"children": [
{
"label": "Bucket name",
Expand All @@ -16,12 +15,17 @@
"evaluationSubstitutionType": "TEMPLATE",
"isRequired": true,
"initialValue": ""
},
{
"label": "Expiry duration of signed URL (minutes)",
"configProperty": "actionConfiguration.formData.create.expiry.data",
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
"initialValue": "5"
}
]
},
{
"controlType": "SECTION",
"label": "Query",
"controlType": "DOUBLE_COLUMN_ZONE",
"description": "Optional",
"children": [
{
Expand All @@ -45,13 +49,13 @@
"value": "NO"
}
]
},
{
"label": "Expiry duration of signed URL (minutes)",
"configProperty": "actionConfiguration.formData.create.expiry.data",
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
"initialValue": "5"
},
}
]
},
{
"controlType": "SINGLE_COLUMN_ZONE",
"description": "Optional",
"children": [
{
"label": "Content",
"configProperty": "actionConfiguration.formData.body.data",
Expand Down
Loading

0 comments on commit 205ba07

Please sign in to comment.