Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: hydrate widgets properties from session on drop #32934

Merged
merged 7 commits into from May 6, 2024

Conversation

jsartisan
Copy link
Contributor

@jsartisan jsartisan commented Apr 25, 2024

This PR adds a ability for anvil widgets ( mainly buttons, icon buttons, heading, paragraph and inline buttons ) to use values from session on creation on drop.

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

Summary by CodeRabbit

  • New Features

    • Introduced logic to maintain button appearance using session storage values.
    • Added functionality to mark properties as reusable, enhancing widget configuration flexibility.
  • Bug Fixes

    • Corrected a syntax error in WidgetCard.tsx to ensure proper class name generation.
  • Enhancements

    • Improved widget creation process by saving and retrieving properties from session storage, ensuring consistency across sessions.

Tip

馃煝 馃煝 馃煝 All cypress tests have passed! 馃帀 馃帀 馃帀
Workflow run: https://github.com/appsmithorg/appsmith/actions/runs/8936335019
Commit: fbe155c
Cypress dashboard url: Click here!

@jsartisan jsartisan requested a review from a team as a code owner April 25, 2024 05:01
@jsartisan jsartisan requested review from riodeuno and removed request for a team April 25, 2024 05:01
Copy link
Contributor

coderabbitai bot commented Apr 25, 2024

Walkthrough

Walkthrough

The recent updates focus on enhancing widget reusability and appearance consistency across the application. Key changes include saving widget properties in session storage for future reuse, introducing a isReusable property to mark configurable elements, and ensuring button styles align with session preferences. These adjustments aim to streamline the widget creation process and maintain visual and functional consistency in the user interface.

Changes

File Path Changes
.../ButtonListControl.tsx Logic added to use session-stored button styles and adjust button variants for WDS_INLINE_BUTTONS_WIDGET.
.../PropertyControlConstants.tsx Added isReusable property to PropertyPaneControlConfig.
.../PropertyControl.tsx Imported getParentWidget and added sessionStorage handling for properties.
.../helpers.tsx, .../WidgetAdditionSagas.ts Functions added for handling property values in session storage during widget creation and retrieval.
.../ZoneWidget/.../propertyPaneStyle.ts, .../WDSButtonWidget/.../styleConfig.ts, .../WDSIconButtonWidget/.../styleConfig.ts, .../WDSInlineButtonsWidget/.../contentConfig.ts, .../WDSParagraphWidget/.../styleConfig.ts, .../WDSRadioGroupWidget/.../contentConfig.ts Added isReusable: true to various config arrays to mark properties as reusable.
.../WDSToolbarButtonsWidget/.../defaultsConfig.ts Added label: "Separator" to button3 in defaultsConfig.
.../WDSRadioGroupWidget/.../contentConfig.ts Updated placeholderText for "Default selected value".

Recent Review Details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits Files that changed from the base of the PR and between c0aaab8 and fbe155c.
Files selected for processing (12)
  • app/client/src/components/propertyControls/ButtonListControl.tsx (3 hunks)
  • app/client/src/constants/PropertyControlConstants.tsx (1 hunks)
  • app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx (3 hunks)
  • app/client/src/pages/Editor/PropertyPane/helpers.tsx (2 hunks)
  • app/client/src/sagas/WidgetAdditionSagas.ts (5 hunks)
  • app/client/src/widgets/anvil/ZoneWidget/widget/config/propertyPaneStyle.ts (1 hunks)
  • app/client/src/widgets/wds/WDSButtonWidget/config/propertyPaneConfig/styleConfig.ts (2 hunks)
  • app/client/src/widgets/wds/WDSIconButtonWidget/config/propertyPaneConfig/styleConfig.ts (2 hunks)
  • app/client/src/widgets/wds/WDSInlineButtonsWidget/config/propertyPaneConfig/contentConfig.ts (2 hunks)
  • app/client/src/widgets/wds/WDSParagraphWidget/config/propertyPaneConfig/styleConfig.ts (3 hunks)
  • app/client/src/widgets/wds/WDSRadioGroupWidget/config/propertyPaneConfig/contentConfig.ts (1 hunks)
  • app/client/src/widgets/wds/WDSToolbarButtonsWidget/config/defaultsConfig.ts (1 hunks)
Files skipped from review due to trivial changes (1)
  • app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx
Files skipped from review as they are similar to previous changes (11)
  • app/client/src/components/propertyControls/ButtonListControl.tsx
  • app/client/src/constants/PropertyControlConstants.tsx
  • app/client/src/pages/Editor/PropertyPane/helpers.tsx
  • app/client/src/sagas/WidgetAdditionSagas.ts
  • app/client/src/widgets/anvil/ZoneWidget/widget/config/propertyPaneStyle.ts
  • app/client/src/widgets/wds/WDSButtonWidget/config/propertyPaneConfig/styleConfig.ts
  • app/client/src/widgets/wds/WDSIconButtonWidget/config/propertyPaneConfig/styleConfig.ts
  • app/client/src/widgets/wds/WDSInlineButtonsWidget/config/propertyPaneConfig/contentConfig.ts
  • app/client/src/widgets/wds/WDSParagraphWidget/config/propertyPaneConfig/styleConfig.ts
  • app/client/src/widgets/wds/WDSRadioGroupWidget/config/propertyPaneConfig/contentConfig.ts
  • app/client/src/widgets/wds/WDSToolbarButtonsWidget/config/defaultsConfig.ts

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the skip-changelog Adding this label to a PR prevents it from being listed in the changelog label Apr 25, 2024
@jsartisan
Copy link
Contributor Author

/build-deploy-preview skip-tests=true

Copy link

Deploying Your Preview: https://github.com/appsmithorg/appsmith/actions/runs/8827251237.
Workflow: On demand build Docker image and deploy preview.
skip-tests: true.
env: ``.
PR: 32934.
recreate: .

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Comment on lines 139 to 186
export function savePropertyInSessionStorageIfRequired(props: {
isReusable: boolean;
widgetProperties: any;
propertyName: string;
propertyValue: string;
}) {
const { isReusable, propertyName, propertyValue, widgetProperties } = props;

if (isReusable && isDynamicValue(propertyValue) === false) {
let widgetType = widgetProperties.type;
let widgetPropertyName = propertyName;

// in case of type is WDS_ICON_BUTTON_WIDGET, we need to use key WDS_BUTTON_WIDGET, reason being
// we want to reuse the property values of icon button for button as well when we create button widget on drop
if (widgetType === "WDS_ICON_BUTTON_WIDGET") {
widgetType = "WDS_BUTTON_WIDGET";
}

// in case of type is WDS_INLINE_BUTTONS_WIDGET, we want to just store the property that is being changed, not the whole property path
if (widgetType === "WDS_INLINE_BUTTONS_WIDGET") {
widgetPropertyName = propertyName.split(".").pop() as string;
}

sessionStorage.setItem(
`${widgetType}.${widgetPropertyName}`,
propertyValue,
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review the implementation of savePropertyInSessionStorageIfRequired for potential improvements in handling different widget types and properties.

- if (widgetType === "WDS_ICON_BUTTON_WIDGET") {
+ if (widgetType === WidgetTypes.ICON_BUTTON) {
- if (widgetType === "WDS_INLINE_BUTTONS_WIDGET") {
+ if (widgetType === WidgetTypes.INLINE_BUTTONS) {

Committable suggestion

鈥硷笍 IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export function savePropertyInSessionStorageIfRequired(props: {
isReusable: boolean;
widgetProperties: any;
propertyName: string;
propertyValue: string;
}) {
const { isReusable, propertyName, propertyValue, widgetProperties } = props;
if (isReusable && isDynamicValue(propertyValue) === false) {
let widgetType = widgetProperties.type;
let widgetPropertyName = propertyName;
// in case of type is WDS_ICON_BUTTON_WIDGET, we need to use key WDS_BUTTON_WIDGET, reason being
// we want to reuse the property values of icon button for button as well when we create button widget on drop
if (widgetType === "WDS_ICON_BUTTON_WIDGET") {
widgetType = "WDS_BUTTON_WIDGET";
}
// in case of type is WDS_INLINE_BUTTONS_WIDGET, we want to just store the property that is being changed, not the whole property path
if (widgetType === "WDS_INLINE_BUTTONS_WIDGET") {
widgetPropertyName = propertyName.split(".").pop() as string;
}
sessionStorage.setItem(
`${widgetType}.${widgetPropertyName}`,
propertyValue,
);
}
}
export function savePropertyInSessionStorageIfRequired(props: {
isReusable: boolean;
widgetProperties: any;
propertyName: string;
propertyValue: string;
}) {
const { isReusable, propertyName, propertyValue, widgetProperties } = props;
if (isReusable && isDynamicValue(propertyValue) === false) {
let widgetType = widgetProperties.type;
let widgetPropertyName = propertyName;
// in case of type is WDS_ICON_BUTTON_WIDGET, we need to use key WDS_BUTTON_WIDGET, reason being
// we want to reuse the property values of icon button for button as well when we create button widget on drop
if (widgetType === WidgetTypes.ICON_BUTTON) {
widgetType = "WDS_BUTTON_WIDGET";
}
// in case of type is WDS_INLINE_BUTTONS_WIDGET, we want to just store the property that is being changed, not the whole property path
if (widgetType === WidgetTypes.INLINE_BUTTONS) {
widgetPropertyName = propertyName.split(".").pop() as string;
}
sessionStorage.setItem(
`${widgetType}.${widgetPropertyName}`,
propertyValue,
);
}
}

Comment on lines +220 to +238
// if buttonVariant and buttonColor values ar present in session storage, then we should use those values
const buttonVariantSessionValue = sessionStorage.getItem(
"WDS_INLINE_BUTTONS_WIDGET.buttonVariant",
);
const buttonColorSessionValue = sessionStorage.getItem(
"WDS_INLINE_BUTTONS_WIDGET.buttonColor",
);

groupButtons[newGroupButtonId] = {
...groupButtons[newGroupButtonId],
buttonVariant: buttonVariantSessionValue || "filled",
buttonColor: buttonColorSessionValue || "accent",
};

// if the widget is a WDS_INLINE_BUTTONS_WIDGET, and button already have filled button variant in groupButtons,
// then we should add a secondary button ( outlined button ) instead of simple button
const filledButtonVariant = groupButtonsArray.find(
(groupButton: any) => groupButton.buttonVariant === "filled",
);

if (filledButtonVariant) {
groupButtons[newGroupButtonId] = {
...groupButtons[newGroupButtonId],
buttonVariant: "outlined",
buttonVariant: buttonVariantSessionValue || "outlined",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review the logic for retrieving and applying session storage values for buttonVariant and buttonColor. Ensure it correctly handles scenarios where session values are not present.

- const buttonVariantSessionValue = sessionStorage.getItem("WDS_INLINE_BUTTONS_WIDGET.buttonVariant");
- const buttonColorSessionValue = sessionStorage.getItem("WDS_INLINE_BUTTONS_WIDGET.buttonColor");
+ const buttonVariantSessionValue = sessionStorage.getItem(`${WidgetTypes.INLINE_BUTTONS}.buttonVariant`);
+ const buttonColorSessionValue = sessionStorage.getItem(`${WidgetTypes.INLINE_BUTTONS}.buttonColor`);

Committable suggestion

鈥硷笍 IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
// if buttonVariant and buttonColor values ar present in session storage, then we should use those values
const buttonVariantSessionValue = sessionStorage.getItem(
"WDS_INLINE_BUTTONS_WIDGET.buttonVariant",
);
const buttonColorSessionValue = sessionStorage.getItem(
"WDS_INLINE_BUTTONS_WIDGET.buttonColor",
);
groupButtons[newGroupButtonId] = {
...groupButtons[newGroupButtonId],
buttonVariant: buttonVariantSessionValue || "filled",
buttonColor: buttonColorSessionValue || "accent",
};
// if the widget is a WDS_INLINE_BUTTONS_WIDGET, and button already have filled button variant in groupButtons,
// then we should add a secondary button ( outlined button ) instead of simple button
const filledButtonVariant = groupButtonsArray.find(
(groupButton: any) => groupButton.buttonVariant === "filled",
);
if (filledButtonVariant) {
groupButtons[newGroupButtonId] = {
...groupButtons[newGroupButtonId],
buttonVariant: "outlined",
buttonVariant: buttonVariantSessionValue || "outlined",
// if buttonVariant and buttonColor values ar present in session storage, then we should use those values
const buttonVariantSessionValue = sessionStorage.getItem(
`${WidgetTypes.INLINE_BUTTONS}.buttonVariant`,
);
const buttonColorSessionValue = sessionStorage.getItem(
`${WidgetTypes.INLINE_BUTTONS}.buttonColor`,
);
groupButtons[newGroupButtonId] = {
...groupButtons[newGroupButtonId],
buttonVariant: buttonVariantSessionValue || "filled",
buttonColor: buttonColorSessionValue || "accent",
};
// if the widget is a WDS_INLINE_BUTTONS_WIDGET, and button already have filled button variant in groupButtons,
// then we should add a secondary button ( outlined button ) instead of simple button
const filledButtonVariant = groupButtonsArray.find(
(groupButton: any) => groupButton.buttonVariant === "filled",
);
if (filledButtonVariant) {
groupButtons[newGroupButtonId] = {
...groupButtons[newGroupButtonId],
buttonVariant: buttonVariantSessionValue || "outlined",

Comment on lines 793 to 578
/**
* retrieves the values from session storage for the widget properties
* for hydration of the widget when we create widget on drop
*/
export function getWidgetSessionValues(type: string) {
// we don't need to hydrate widget for following widgets
if (["WDS_INLINE_BUTTONS_WIDGET"].includes(type)) return;

let widgetType = type;
const configMap = WidgetFactory.widgetConfigMap.get(type);
const widgetSessionValues: any = {};

// in case we are dropping WDS_ICON_BUTTON_WIDGET, we want to reuse the values of BUTTON_WIDGET
if (type === "WDS_ICON_BUTTON_WIDGET") {
widgetType = "WDS_BUTTON_WIDGET";
}

for (const key in configMap) {
if (configMap[key]) {
const valueFromSession = sessionStorage.getItem(`${widgetType}.${key}`);

if (valueFromSession) {
widgetSessionValues[key] = valueFromSession;
}
}
}

return widgetSessionValues;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review the implementation of getWidgetSessionValues for potential improvements in handling different widget types and properties.

- if (["WDS_INLINE_BUTTONS_WIDGET"].includes(type)) return;
+ if ([WidgetTypes.INLINE_BUTTONS].includes(type)) return;
- if (type === "WDS_ICON_BUTTON_WIDGET") {
+ if (type === WidgetTypes.ICON_BUTTON) {

Committable suggestion

鈥硷笍 IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
/**
* retrieves the values from session storage for the widget properties
* for hydration of the widget when we create widget on drop
*/
export function getWidgetSessionValues(type: string) {
// we don't need to hydrate widget for following widgets
if (["WDS_INLINE_BUTTONS_WIDGET"].includes(type)) return;
let widgetType = type;
const configMap = WidgetFactory.widgetConfigMap.get(type);
const widgetSessionValues: any = {};
// in case we are dropping WDS_ICON_BUTTON_WIDGET, we want to reuse the values of BUTTON_WIDGET
if (type === "WDS_ICON_BUTTON_WIDGET") {
widgetType = "WDS_BUTTON_WIDGET";
}
for (const key in configMap) {
if (configMap[key]) {
const valueFromSession = sessionStorage.getItem(`${widgetType}.${key}`);
if (valueFromSession) {
widgetSessionValues[key] = valueFromSession;
}
}
}
return widgetSessionValues;
}
/**
* retrieves the values from session storage for the widget properties
* for hydration of the widget when we create widget on drop
*/
export function getWidgetSessionValues(type: string) {
// we don't need to hydrate widget for following widgets
if ([WidgetTypes.INLINE_BUTTONS].includes(type)) return;
let widgetType = type;
const configMap = WidgetFactory.widgetConfigMap.get(type);
const widgetSessionValues: any = {};
// in case we are dropping WDS_ICON_BUTTON_WIDGET, we want to reuse the values of BUTTON_WIDGET
if (type === WidgetTypes.ICON_BUTTON) {
widgetType = "WDS_BUTTON_WIDGET";
}
for (const key in configMap) {
if (configMap[key]) {
const valueFromSession = sessionStorage.getItem(`${widgetType}.${key}`);
if (valueFromSession) {
widgetSessionValues[key] = valueFromSession;
}
}
}
return widgetSessionValues;
}

Copy link

Deploy-Preview-URL: https://ce-32934.dp.appsmith.com

@jsartisan jsartisan requested review from marks0351 and removed request for riodeuno April 25, 2024 05:40
@jsartisan jsartisan force-pushed the chore/preserve-semantic-properties branch from 152ac84 to 1e113f9 Compare April 25, 2024 07:13
if (this.props.widgetProperties.type === "WDS_INLINE_BUTTONS_WIDGET") {
// if buttonVariant and buttonColor values ar present in session storage, then we should use those values
const buttonVariantSessionValue = sessionStorage.getItem(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsartisan I would suggest we create generic methods to fetch widget based default values modifiers for property controls and use them to manipulate the default values set in the property controls. this would keep the widget related logic to pick the deafult value within the widget. like how we do for pasting(pasteOperationChecks) in Anvil. coz soon we might wanna do this for any property of a widget that is set from any property contorl.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marks0351 @jsartisan Any reason we approved & merged this PR without resolving this comment? Was this suggestion no longer relevant? If yes, can we please ensure we update these comments on Github before resolving & merging the PR? It ensures that everyone is on the same page.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mohanarpit we had discussed to do this as a follow up task as it required more detailing. @jsartisan can you pls mention the task here.

@@ -125,6 +125,7 @@ function* getChildWidgetProps(
]);
const themeDefaultConfig =
WidgetFactory.getWidgetStylesheetConfigMap(type) || {};
const widgetSessionValues = getWidgetSessionValues(type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make sure these valyes are only used for Anvil

@jsartisan jsartisan force-pushed the chore/preserve-semantic-properties branch from 449ec5d to a50624c Compare April 30, 2024 10:42
@jsartisan jsartisan force-pushed the chore/preserve-semantic-properties branch from a50624c to fbe155c Compare May 1, 2024 04:56
@@ -574,6 +579,15 @@ const PropertyControl = memo((props: Props) => {
// updating properties of a widget(s) should be done only once when property value changes.
// to make sure dsl updates are atomic which is a necessity for undo/redo.
onBatchUpdatePropertiesOfMultipleWidgets(allPropertiesToUpdates);

savePropertyInSessionStorageIfRequired({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should take a dispatch call, listen for it from a saga and set the session only for Anvil layout?
WDYT

widgetProperties,
} = props;

if (isReusable && isDynamicValue(propertyValue) === false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsartisan in the follow up task I am assuming this part can also be fetched from individual widgets

@@ -130,6 +142,7 @@ function* getChildWidgetProps(
widgetId: newWidgetId,
renderMode: RenderModes.CANVAS,
...themeDefaultConfig,
...widgetSessionValues,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be adding this only for Anvil

Copy link
Contributor Author

@jsartisan jsartisan May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for now, I have used isReusable property on anvil widgets only. So we should be good.

@jsartisan jsartisan added the ok-to-test Required label for CI label May 3, 2024
@jsartisan jsartisan merged commit 905e5b7 into release May 6, 2024
43 checks passed
@jsartisan jsartisan deleted the chore/preserve-semantic-properties branch May 6, 2024 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ok-to-test Required label for CI skip-changelog Adding this label to a PR prevents it from being listed in the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants