Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9e6417d
feat: show infinite scroll only when serverSidePagination is enabled
jacquesikot Mar 26, 2025
86bd4b0
feat: Add disabled state and disabled help text for property sections…
jacquesikot Mar 26, 2025
52dfadf
Merge branch 'release' of github.com:appsmithorg/appsmith into feat/a…
jacquesikot Mar 26, 2025
6132745
Revert "feat: show infinite scroll only when serverSidePagination is …
jacquesikot Mar 26, 2025
19c3620
refactor: Simplify PropertyControl component structure
jacquesikot Mar 26, 2025
7a3e6cc
test: add tests for disabled and disabledHelpText implementation
jacquesikot Mar 26, 2025
845406a
fix: disabled prop type mismatch between PropertyPaneControlConfig an…
jacquesikot Mar 26, 2025
5a57c17
refactor: Update imports and context usage in property controls to fi…
jacquesikot Mar 26, 2025
b1adca0
Merge branch 'release' of github.com:appsmithorg/appsmith into feat/a…
jacquesikot Mar 27, 2025
d8cba00
refactor: Clean up formatting and improve readability in property con…
jacquesikot Mar 27, 2025
14db8bb
refactor: Update imports in PropertySection component
jacquesikot Mar 27, 2025
3536352
fix: lint error
jacquesikot Mar 27, 2025
2a75ea6
fix: lint error
jacquesikot Mar 27, 2025
864f329
refactor: Rename isReadOnly to isControlDisabled and update type defi…
jacquesikot Mar 31, 2025
007a525
refactor: Update type definitions for hidden and disabled properties …
jacquesikot Mar 31, 2025
5a68737
refactor: Update type definitions for hidden and disabled properties …
jacquesikot Mar 31, 2025
f79ccfd
refactor: Update type definitions for hidden and disabled properties …
jacquesikot Mar 31, 2025
d4f7b64
refactor: Update type definitions for hidden and disabled properties …
jacquesikot Mar 31, 2025
aa2b8fc
refactor: Update hidden and disabled property type definitions in Pro…
jacquesikot Mar 31, 2025
2164882
refactor: Reorganize imports in PropertyControl component
jacquesikot Mar 31, 2025
17a09a3
Merge branch 'release' of github.com:appsmithorg/appsmith into feat/a…
jacquesikot Apr 1, 2025
7b5cd76
refactor: rename disabled property to shouldDisableSection in propert…
jacquesikot Apr 1, 2025
efa3347
refactor: remove disabled properties from pagination section in Table…
jacquesikot Apr 1, 2025
1e656cf
refactor: sort props alphabetically
jacquesikot Apr 1, 2025
08429fe
refactor: reorder props in CodeEditorControl component
jacquesikot Apr 1, 2025
285d46d
refactor: remove unused marking prop from CodeEditorControl component
jacquesikot Apr 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions app/client/src/components/propertyControls/CodeEditorControl.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type { ChangeEvent } from "react";
import React from "react";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
import type { EventOrValueHandler } from "redux-form";
import { slashCommandHintHelper } from "components/editorComponents/CodeEditor/commandsHelper";
import {
EditorModes,
EditorSize,
EditorTheme,
TabBehaviour,
} from "components/editorComponents/CodeEditor/EditorConfig";
import LazyCodeEditor from "components/editorComponents/LazyCodeEditor";
import { bindingHintHelper } from "components/editorComponents/CodeEditor/hintHelpers";
import { slashCommandHintHelper } from "components/editorComponents/CodeEditor/commandsHelper";
import type { EditorProps } from "components/editorComponents/CodeEditor";
import LazyCodeEditor from "components/editorComponents/LazyCodeEditor";
import type { ChangeEvent } from "react";
import React from "react";
import type { EventOrValueHandler } from "redux-form";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";

class CodeEditorControl extends BaseControl<ControlProps> {
render() {
Expand All @@ -21,31 +21,41 @@ class CodeEditorControl extends BaseControl<ControlProps> {
evaluatedValue,
expected,
propertyValue,
shouldDisableSection,
useValidationMessage,
} = this.props;

const props: Partial<ControlProps> = {};
// PropertyPaneControlConfig's disabled is a function (props: any, propertyPath: string) => boolean
// while LazyCodeEditor expects a boolean. Convert function to boolean result.
const isControlDisabled =
typeof shouldDisableSection === "function"
? shouldDisableSection(
this.props.widgetProperties,
this.props.propertyName,
)
: !!shouldDisableSection;

if (dataTreePath) props.dataTreePath = dataTreePath;

if (evaluatedValue) props.evaluatedValue = evaluatedValue;

if (expected) props.expected = expected;
const maxHeight = controlConfig?.maxHeight
? String(controlConfig.maxHeight)
: undefined;

return (
<LazyCodeEditor
AIAssisted
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice work refactoring this!

additionalDynamicData={this.props.additionalAutoComplete}
dataTreePath={dataTreePath}
evaluatedValue={evaluatedValue}
expected={expected}
hinting={[bindingHintHelper, slashCommandHintHelper]}
input={{ value: propertyValue, onChange: this.onChange }}
maxHeight={controlConfig?.maxHeight as EditorProps["maxHeight"]}
isReadOnly={isControlDisabled}
maxHeight={maxHeight}
mode={EditorModes.TEXT_WITH_BINDING}
positionCursorInsideBinding
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INDENT}
theme={this.props.theme}
theme={EditorTheme.LIGHT}
useValidationMessage={useValidationMessage}
{...props}
AIAssisted
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import React from "react";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
import { StyledDynamicInput } from "./StyledControls";
import type { InputType } from "components/constants";
import type { CodeEditorExpected } from "components/editorComponents/CodeEditor";
import { slashCommandHintHelper } from "components/editorComponents/CodeEditor/commandsHelper";
import type { FieldEntityInformation } from "components/editorComponents/CodeEditor/EditorConfig";
import {
CodeEditorBorder,
Expand All @@ -12,11 +9,14 @@ import {
EditorTheme,
TabBehaviour,
} from "components/editorComponents/CodeEditor/EditorConfig";
import { CollapseContext } from "pages/Editor/PropertyPane/PropertySection";
import LazyCodeEditor from "../editorComponents/LazyCodeEditor";
import type { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator";
import { bindingHintHelper } from "components/editorComponents/CodeEditor/hintHelpers";
import { slashCommandHintHelper } from "components/editorComponents/CodeEditor/commandsHelper";
import { CollapseContext } from "pages/Editor/PropertyPane/PropertyPaneContexts";
import React from "react";
import type { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator";
import LazyCodeEditor from "../editorComponents/LazyCodeEditor";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
import { StyledDynamicInput } from "./StyledControls";

export function InputText(props: {
label: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import React from "react";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
import { StyledDynamicInput } from "./StyledControls";
import type {
CodeEditorExpected,
EditorProps,
} from "components/editorComponents/CodeEditor";
import { slashCommandHintHelper } from "components/editorComponents/CodeEditor/commandsHelper";
import {
CodeEditorBorder,
EditorModes,
EditorSize,
EditorTheme,
TabBehaviour,
} from "components/editorComponents/CodeEditor/EditorConfig";
import type { ColumnProperties } from "widgets/TableWidgetV2/component/Constants";
import { isDynamicValue } from "utils/DynamicBindingUtils";
import { bindingHintHelper } from "components/editorComponents/CodeEditor/hintHelpers";
import LazyCodeEditor from "components/editorComponents/LazyCodeEditor";
import { CollapseContext } from "pages/Editor/PropertyPane/PropertyPaneContexts";
import React from "react";
import styled from "styled-components";
import type { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator";
import { isDynamicValue } from "utils/DynamicBindingUtils";
import { isString } from "utils/helpers";
import type { ColumnProperties } from "widgets/TableWidgetV2/component/Constants";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
import { StyledDynamicInput } from "./StyledControls";
import { JSToString, stringToJS } from "./utils";
import type { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator";
import LazyCodeEditor from "components/editorComponents/LazyCodeEditor";
import { bindingHintHelper } from "components/editorComponents/CodeEditor/hintHelpers";
import { slashCommandHintHelper } from "components/editorComponents/CodeEditor/commandsHelper";
import { CollapseContext } from "pages/Editor/PropertyPane/PropertySection";

const PromptMessage = styled.span`
line-height: 17px;
Expand Down
27 changes: 27 additions & 0 deletions app/client/src/constants/PropertyControlConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ export interface PropertyPaneSectionConfig {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hidden?: (props: any, propertyPath: string) => boolean;
/**
* @param props - Current widget properties
* @param propertyPath - Path to the widget property
* @returns - True if the section should be disabled, false otherwise
*/
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this? Let's not use any; you can prefer using unknown but since these are WidgetProps, do you think we can use that without any cyclic dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have tried widget props and I still get a lot of errors on CI - build failure due to type check.

shouldDisableSection?: (props: any, propertyPath: string) => boolean;
/**
* Help text to show when section is disabled.
* Appears as a tooltip when hovering over the disabled section.
*/
disabledHelpText?: string;
/**
* when true, the section will be open by default.
* Note: Seems like this is not used anywhere.
Expand Down Expand Up @@ -205,6 +218,20 @@ export interface PropertyPaneControlConfig {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hidden?: (props: any, propertyPath: string) => boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Update this property to use more specific typing.

Similar to line 53, this property should be updated from any to Record<string, unknown> for consistency across the interface and improved type safety.

-  // TODO: Fix this the next time the file is edited
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  hidden?: (props: any, propertyPath: string) => boolean;
+  hidden?: (props: Record<string, unknown>, propertyPath: string) => boolean;

Committable suggestion skipped: line range outside the PR's diff.

/**
* callback function to determine if the property should be disabled.

* @param props - Current widget properties
* @param propertyPath - Path to the widget property
* @returns - True if the property should be disabled, false otherwise
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
shouldDisableSection?: (props: any, propertyPath: string) => boolean;
/**
* Help text to show when property is disabled.
* Appears as a tooltip when hovering over the disabled property.
*/
disabledHelpText?: string;
/**
* If true, the property is hidden.
* Note: hidden and invisible do the same thing but differently. hidden uses a callback to determine if the property should be hidden.
Expand Down
124 changes: 124 additions & 0 deletions app/client/src/pages/Editor/PropertyPane/PropertyControl.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React from "react";
import { render } from "@testing-library/react";
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
import type { IPanelProps } from "@blueprintjs/core";
import type { WidgetProps } from "widgets/BaseWidget";
import PropertyControl from "./PropertyControl";
import type { EnhancementFns } from "selectors/widgetEnhancementSelectors";

interface MockPropertyControlProps {
shouldDisableSection?: (
widgetProperties: WidgetProps,
propertyName: string,
) => boolean;
disabledHelpText?: string;
label: string;
propertyName: string;
widgetProperties: WidgetProps;
}

const MockPropertyControl = (props: MockPropertyControlProps) => {
const isDisabled = props.shouldDisableSection
? props.shouldDisableSection(props.widgetProperties, props.propertyName)
: false;

return (
<div
className={isDisabled ? "cursor-not-allowed opacity-50" : ""}
data-testid="t--property-control-wrapper"
>
<label>{props.label}</label>
<input
data-testid="t--property-input"
disabled={isDisabled}
role="textbox"
type="text"
/>
{isDisabled && props.disabledHelpText && (
<div data-testid="t--disabled-help-text">{props.disabledHelpText}</div>
)}
</div>
);
};

jest.mock("./PropertyControl", () => (props: MockPropertyControlProps) => (
<MockPropertyControl {...props} />
));

describe("PropertyControl", () => {
const mockPanel: IPanelProps = {
closePanel: jest.fn(),
openPanel: jest.fn(),
};

const defaultProps = {
controlType: "INPUT_TEXT",
enhancements: undefined as EnhancementFns | undefined,
isBindProperty: true,
isSearchResult: false,
isTriggerProperty: false,
label: "Test Label",
panel: mockPanel,
propertyName: "testProperty",
theme: EditorTheme.LIGHT,
widgetProperties: {
testProperty: "test value",
type: "CONTAINER_WIDGET",
widgetId: "test-widget",
widgetName: "TestWidget",
},
};

it("should render property control normally when not disabled", () => {
const { getByTestId } = render(<PropertyControl {...defaultProps} />);

expect(
(getByTestId("t--property-input") as HTMLInputElement).disabled,
).toBe(false);
expect(getByTestId("t--property-control-wrapper").className).toBe("");
});

it("should render disabled property control when disabled prop is true", () => {
const disabledProps = {
...defaultProps,
shouldDisableSection: () => true,
};

const { getByTestId } = render(<PropertyControl {...disabledProps} />);

const wrapper = getByTestId("t--property-control-wrapper");

expect(wrapper.classList.contains("cursor-not-allowed")).toBe(true);
expect(wrapper.classList.contains("opacity-50")).toBe(true);
expect(
(getByTestId("t--property-input") as HTMLInputElement).disabled,
).toBe(true);
});

it("should show disabled help text when property is disabled", () => {
const disabledProps = {
...defaultProps,
shouldDisableSection: () => true,
disabledHelpText: "This property is disabled because...",
};

const { getByTestId } = render(<PropertyControl {...disabledProps} />);

expect(getByTestId("t--disabled-help-text")).toBeTruthy();
expect(getByTestId("t--disabled-help-text").textContent).toBe(
"This property is disabled because...",
);
});

it("should not show disabled help text when property is not disabled", () => {
const props = {
...defaultProps,
shouldDisableSection: () => false,
disabledHelpText: "This property is disabled because...",
};

const { queryByTestId } = render(<PropertyControl {...props} />);

expect(queryByTestId("t--disabled-help-text")).toBeFalsy();
});
});
Loading
Loading