Skip to content

Commit

Permalink
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Browse files Browse the repository at this point in the history
…git-pkg-4
  • Loading branch information
brayn003 committed Feb 17, 2025
2 parents 6f57702 + 07187a7 commit 13aa020
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,58 @@ describe(
deployMode.NavigateBacktoEditor();
});

it("2. Toggle JS - Validate isVisible", function () {
it("2. Validate isVisible when list has selected item (#37683)", () => {
// Define selectors for widgets
const widgetSelector = (name: string) => `[data-widgetname-cy="${name}"]`;
const containerWidgetSelector = `[type="CONTAINER_WIDGET"]`;

// Drag and drop the Button widget onto the canvas
// entityExplorer.DragDropWidgetNVerify("listwidgetv2", 300, 300);
entityExplorer.DragDropWidgetNVerify("buttonwidget");

// Set up the button to make the list visible when clicked
EditorNavigation.SelectEntityByName("Button1", EntityType.Widget);
propPane.EnterJSContext("onClick", `{{List1.setVisibility(true)}}`, true);

// Set up the list widget to become invisible when an item is clicked
EditorNavigation.SelectEntityByName("List1", EntityType.Widget);
propPane.EnterJSContext(
"onItemClick",
`{{List1.setVisibility(false)}}`,
true,
);

// Deploy the application to test the visibility functionality
deployMode.DeployApp();

// Simulate a click on the first item in the list to hide the list
agHelper
.GetElement(widgetSelector("List1"))
.find(containerWidgetSelector)
.first()
.click({ force: true });
agHelper.WaitUntilEleDisappear(
locators._widgetInDeployed(draggableWidgets.LIST_V2),
);

// Assert that the list widget is not visible after clicking an item
agHelper.AssertElementAbsence(
locators._widgetInDeployed(draggableWidgets.LIST_V2),
);

// Click the button to make the list visible again
agHelper.GetNClick(widgetSelector("Button1"));

// Assert that the list widget is visible after clicking the button
agHelper.AssertElementVisibility(
locators._widgetInDeployed(draggableWidgets.LIST_V2),
);

// Navigate back to the editor after testing
deployMode.NavigateBacktoEditor();
});

it("3. Toggle JS - Validate isVisible", function () {
// Open Property pane
agHelper.AssertElementVisibility(
locators._widgetInDeployed(draggableWidgets.LIST_V2),
Expand All @@ -61,7 +112,7 @@ describe(
deployMode.NavigateBacktoEditor();
});

it("3. Renaming the widget from Property pane and Entity explorer ", function () {
it("4. Renaming the widget from Property pane and Entity explorer ", function () {
// Open Property pane
EditorNavigation.SelectEntityByName("List1", EntityType.Widget);
// Change the list widget name from property pane and Verify it
Expand All @@ -73,7 +124,7 @@ describe(
agHelper.AssertElementVisibility(locators._widgetName("List1"));
});

it("4. Item Spacing Validation ", function () {
it("5. Item Spacing Validation ", function () {
EditorNavigation.SelectEntityByName("List1", EntityType.Widget);
propPane.Search("item spacing");
propPane.UpdatePropertyFieldValue("Item Spacing (px)", "-1");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ControlType } from "constants/PropertyControlConstants";
import React from "react";
import { FieldArray } from "redux-form";
import type { ControlProps } from "../BaseControl";
import BaseControl from "../BaseControl";
import { FunctionCallingConfigForm } from "./components/FunctionCallingConfigForm";

/**
* This component is used to configure the function calling for the AI assistant.
* It allows the user to add, edit and delete functions that can be used by the AI assistant.
*/
export default class FunctionCallingConfigControl extends BaseControl<ControlProps> {
render() {
return (
<FieldArray
component={FunctionCallingConfigForm}
key={this.props.configProperty}
name={this.props.configProperty}
props={{
formName: this.props.formName,
configProperty: this.props.configProperty,
}}
rerenderOnEveryChange={false}
/>
);
}

getControlType(): ControlType {
return "FUNCTION_CALLING_CONFIG_FORM";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Button, Text } from "@appsmith/ads";
import { default as React, useCallback } from "react";
import type { FieldArrayFieldsProps } from "redux-form";
import styled from "styled-components";
import { v4 as uuid } from "uuid";
import type { FunctionCallingConfigFormToolField } from "../types";
import { FunctionCallingConfigToolField } from "./FunctionCallingConfigToolField";
import { FunctionCallingEmpty } from "./FunctionCallingEmpty";

export interface FunctionCallingConfigFormProps {
formName: string;
fields: FieldArrayFieldsProps<FunctionCallingConfigFormToolField>;
}

const Header = styled.div`
display: flex;
gap: var(--ads-v2-spaces-4);
justify-content: space-between;
margin-bottom: var(--ads-v2-spaces-4);
`;

const ConfigItems = styled.div`
display: flex;
flex-direction: column;
gap: var(--ads-v2-spaces-4);
`;

export const FunctionCallingConfigForm = ({
fields,
formName,
}: FunctionCallingConfigFormProps) => {
const handleAddFunctionButtonClick = useCallback(() => {
fields.push({
id: uuid(),
description: "",
entityId: "",
requiresApproval: false,
entityType: "Query",
});
}, [fields]);

const handleRemoveToolButtonClick = useCallback(
(index: number) => {
fields.remove(index);
},
[fields],
);

return (
<>
<Header>
<div>
<Text isBold kind="heading-s" renderAs="p">
Function Calls
</Text>
<Text renderAs="p">
Add functions for the model to execute dynamically.
</Text>
</div>

<Button
kind="secondary"
onClick={handleAddFunctionButtonClick}
startIcon="plus"
>
Add Function
</Button>
</Header>

{fields.length === 0 ? (
<FunctionCallingEmpty />
) : (
<ConfigItems>
{fields.map((field, index) => {
return (
<FunctionCallingConfigToolField
fieldPath={field}
formName={formName}
index={index}
key={field}
onRemove={handleRemoveToolButtonClick}
/>
);
})}
</ConfigItems>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { Button, Text } from "@appsmith/ads";
import type { AppState } from "ee/reducers";
import FormControl from "pages/Editor/FormControl";
import React, { useCallback, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { change, formValueSelector } from "redux-form";
import styled from "styled-components";
import type { DropDownGroupedOptions } from "../../DropDownControl";
import type {
FunctionCallingEntityTypeOption,
FunctionCallingEntityType,
} from "../types";
import { selectEntityOptions } from "./selectors";

interface FunctionCallingConfigToolFieldProps {
fieldPath: string;
formName: string;
index: number;
onRemove: (index: number) => void;
}

const ConfigItemRoot = styled.div`
display: flex;
flex-direction: column;
padding: var(--ads-v2-spaces-3);
border-radius: var(--ads-v2-border-radius);
border: 1px solid var(--ads-v2-color-border);
background: var(--colors-semantics-bg-inset, #f8fafc);
`;

const ConfigItemRow = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: var(--ads-v2-spaces-4);
justify-content: space-between;
margin-bottom: var(--ads-v2-spaces-4);
`;

const ConfigItemSelectWrapper = styled.div`
width: 50%;
min-width: 160px;
`;

export const FunctionCallingConfigToolField = ({
index,
onRemove,
...props
}: FunctionCallingConfigToolFieldProps) => {
const dispatch = useDispatch();
const fieldValue = useSelector((state: AppState) =>
formValueSelector(props.formName)(state, props.fieldPath),
);
const entityOptions = useSelector(selectEntityOptions);

const handleRemoveButtonClick = useCallback(() => {
onRemove(index);
}, [onRemove, index]);

const findEntityOption = useCallback(
(entityId: string, items: FunctionCallingEntityTypeOption[]): boolean => {
return items.find(({ value }) => value === entityId) !== undefined;
},
[entityOptions],
);

const findEntityType = useCallback(
(entityId: string): string => {
switch (true) {
case findEntityOption(entityId, entityOptions.Query):
return "Query";
case findEntityOption(entityId, entityOptions.JSFunction):
return "JSFunction";
case findEntityOption(entityId, entityOptions.SystemFunction):
return "SystemFunction";
}

return "";
},
[fieldValue.entityId, entityOptions],
);

useEffect(
// entityType is dependent on entityId
// Every time entityId changes, we need to find the new entityType
function handleEntityTypeChange() {
const entityType = findEntityType(fieldValue.entityId);

dispatch(
change(props.formName, `${props.fieldPath}.entityType`, entityType),
);
},
[fieldValue.entityId],
);

return (
<ConfigItemRoot>
<ConfigItemRow>
<Text isBold renderAs="p">
Function
</Text>
<Button
isIconButton
kind="tertiary"
onClick={handleRemoveButtonClick}
startIcon="trash"
/>
</ConfigItemRow>
<ConfigItemRow>
<ConfigItemSelectWrapper>
<FormControl
config={{
controlType: "DROP_DOWN",
configProperty: `${props.fieldPath}.entityId`,
formName: props.formName,
id: props.fieldPath,
label: "",
isValid: true,
// @ts-expect-error FormControl component has incomplete TypeScript definitions for some valid properties
isSearchable: true,
options: [
...entityOptions.Query,
...entityOptions.JSFunction,
...entityOptions.SystemFunction,
],
optionGroupConfig: {
Query: {
label: "Queries",
children: [],
},
JSFunction: {
label: "JS Functions",
children: [],
},
SystemFunction: {
label: "System Functions",
children: [],
},
} satisfies Record<
FunctionCallingEntityType,
DropDownGroupedOptions
>,
}}
formName={props.formName}
/>
</ConfigItemSelectWrapper>
<FormControl
config={{
controlType: "SWITCH",
configProperty: `${props.fieldPath}.requiresApproval`,
formName: props.formName,
id: props.fieldPath,
label: "Requires approval",
isValid: true,
}}
formName={props.formName}
/>
</ConfigItemRow>
<FormControl
config={{
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
configProperty: `${props.fieldPath}.description`,
formName: props.formName,
id: props.fieldPath,
placeholderText: "Describe how this function should be used...",
label: "Description",
isValid: true,
}}
formName={props.formName}
/>
</ConfigItemRoot>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Text } from "@appsmith/ads";
import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants";
import { getAssetUrl } from "ee/utils/airgapHelpers";
import React, { memo } from "react";
import styled from "styled-components";

const Root = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: var(--ads-v2-spaces-4);
height: 300px;
`;

export const FunctionCallingEmpty = memo(() => {
return (
<Root>
<img alt="" src={getAssetUrl(`${ASSETS_CDN_URL}/empty-state.svg`)} />
<Text>Function Calls will be displayed here</Text>
</Root>
);
});
Loading

0 comments on commit 13aa020

Please sign in to comment.