Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions app/client/src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2716,10 +2716,14 @@ export const NO_SEARCH_COMMAND_FOUND_EXTERNAL_SAAS = () =>

export const ADD_CUSTOM_ACTION = () => "Add custom action";

export const ADD_CUSTOM_GRAPHQL_ACTION = () => "Add custom GraphQL action";

export const CONFIG_PROPERTY_COMMAND = () => "command";

export const CUSTOM_ACTION_LABEL = () => "Custom Action";

export const CUSTOM_GRAPHQL_ACTION_LABEL = () => "Custom GraphQL Action";

export const AUTH_LOGIN_TOO_MANY_ATTEMPTS = () =>
"Too many login attempts. Please try again after some time.";
export const AUTH_ACCOUNT_SUSPENDED_FOR_RATE_LIMIT = () =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from "react";
import type { ControlType } from "constants/PropertyControlConstants";
import FormControl from "pages/Editor/FormControl";
import { TabPanel, TabsList, Tab } from "@appsmith/ads";
import BaseControl, { type ControlProps } from "../BaseControl";
import { HTTP_METHOD } from "PluginActionEditor/constants/CommonApiConstants";
import { API_EDITOR_TAB_TITLES } from "ee/constants/messages";
import { createMessage } from "ee/constants/messages";
import {
CustomActionFormLayout,
CUSTOM_ACTION_TABS,
TabbedWrapper,
useSyncParamsToPath,
} from "./common";

const TabbedControls = (props: ControlProps) => {
// Use the hook to sync params with path
useSyncParamsToPath(props.formName, props.configProperty);

return (
<TabbedWrapper defaultValue={CUSTOM_ACTION_TABS.HEADERS}>
<TabsList>
{Object.values(CUSTOM_ACTION_TABS).map((tab) => (
<Tab data-testid={`t--api-editor-${tab}`} key={tab} value={tab}>
{createMessage(API_EDITOR_TAB_TITLES[tab])}
</Tab>
))}
</TabsList>

<TabPanel value={CUSTOM_ACTION_TABS.HEADERS}>
<FormControl
config={{
controlType: "KEYVALUE_ARRAY",
configProperty: `${props.configProperty}.headers`,
formName: props.formName,
id: `${props.configProperty}.headers`,
isValid: true,
// @ts-expect-error FormControl component has incomplete TypeScript definitions for some valid properties
showHeader: true,
}}
formName={props.formName}
/>
</TabPanel>
<TabPanel value={CUSTOM_ACTION_TABS.PARAMS}>
<FormControl
config={{
controlType: "KEYVALUE_ARRAY",
configProperty: `${props.configProperty}.params`,
formName: props.formName,
id: `${props.configProperty}.params`,
// @ts-expect-error FormControl component has incomplete TypeScript definitions for some valid properties
showHeader: true,
isValid: true,
}}
formName={props.formName}
/>
</TabPanel>
<TabPanel value={CUSTOM_ACTION_TABS.BODY}>
<FormControl
config={{
controlType: "QUERY_DYNAMIC_TEXT",
configProperty: `${props.configProperty}.body`,
formName: props.formName,
id: `${props.configProperty}.body`,
label: "",
isValid: true,
}}
formName={props.formName}
/>
</TabPanel>
</TabbedWrapper>
);
};

/**
* This component is used to configure the custom actions for the external integration.
* It allows the user to add or update details for the custom action like method type, path, headers, params, body.
*/
export class CustomActionsControl extends BaseControl<ControlProps> {
getControlType(): ControlType {
return "CUSTOM_ACTIONS_CONFIG_FORM";
}
render() {
const { props } = this;

return (
<CustomActionFormLayout
configProperty={props.configProperty}
formName={props.formName}
methodOptions={Object.values(HTTP_METHOD).map((method) => ({
label: method,
value: method,
}))}
pathPlaceholder="/v1/users"
>
<TabbedControls {...props} />
</CustomActionFormLayout>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import React from "react";
import type { ControlType } from "constants/PropertyControlConstants";
import FormControl from "pages/Editor/FormControl";
import { TabPanel, TabsList, Tab } from "@appsmith/ads";
import BaseControl, { type ControlProps } from "../BaseControl";
import { GRAPHQL_HTTP_METHOD_OPTIONS } from "PluginActionEditor/constants/GraphQLEditorConstants";
import { API_EDITOR_TAB_TITLES } from "ee/constants/messages";
import { createMessage } from "ee/constants/messages";
import styled from "styled-components";
import { useSelector } from "react-redux";
import { getFormData } from "selectors/formSelectors";
import {
CodeEditorBorder,
EditorModes,
EditorSize,
EditorTheme,
TabBehaviour,
} from "components/editorComponents/CodeEditor/EditorConfig";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import {
Section,
Zone,
} from "PluginActionEditor/components/PluginActionForm/components/ActionForm";
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
import FormLabel from "components/editorComponents/FormLabel";
import {
CustomActionFormLayout,
CUSTOM_ACTION_TABS,
TabbedWrapper,
useSyncParamsToPath,
} from "./common";

const GraphQLQueryContainer = styled.div`
&&&& .CodeMirror {
height: auto;
min-height: 150px;
}
`;

const StyledFormLabel = styled(FormLabel)`
&& {
margin-bottom: var(--ads-v2-spaces-2);
padding: 0;
}
`;

const EXPECTED_VARIABLE = {
type: "object",
example:
'{\n "name":"{{ inputName.property }}",\n "preference":"{{ dropdownName.property }}"\n}',
autocompleteDataType: AutocompleteDataType.OBJECT,
};

const TabbedControls = (props: ControlProps) => {
// Use the hook to sync params with path
useSyncParamsToPath(props.formName, props.configProperty);

const formValues = useSelector((state) => getFormData(state, props.formName));
const values = formValues?.values || {};
const actionName = values.name || "";

return (
<TabbedWrapper defaultValue={CUSTOM_ACTION_TABS.HEADERS}>
<TabsList>
{Object.values(CUSTOM_ACTION_TABS).map((tab) => {
let tabLabel: string = tab;

if (tab === CUSTOM_ACTION_TABS.HEADERS) {
tabLabel = createMessage(API_EDITOR_TAB_TITLES.HEADERS);
} else if (tab === CUSTOM_ACTION_TABS.PARAMS) {
tabLabel = createMessage(API_EDITOR_TAB_TITLES.PARAMS);
} else if (tab === CUSTOM_ACTION_TABS.BODY) {
tabLabel = createMessage(API_EDITOR_TAB_TITLES.BODY);
}

return (
<Tab data-testid={`t--graphql-editor-${tab}`} key={tab} value={tab}>
{tabLabel}
</Tab>
);
})}
</TabsList>

<TabPanel value={CUSTOM_ACTION_TABS.HEADERS}>
<FormControl
config={{
controlType: "KEYVALUE_ARRAY",
configProperty: `${props.configProperty}.headers`,
formName: props.formName,
id: `${props.configProperty}.headers`,
isValid: true,
// @ts-expect-error FormControl component has incomplete TypeScript definitions for some valid properties
showHeader: true,
}}
formName={props.formName}
/>
</TabPanel>
<TabPanel value={CUSTOM_ACTION_TABS.PARAMS}>
<FormControl
config={{
controlType: "KEYVALUE_ARRAY",
configProperty: `${props.configProperty}.params`,
formName: props.formName,
id: `${props.configProperty}.params`,
// @ts-expect-error FormControl component has incomplete TypeScript definitions for some valid properties
showHeader: true,
isValid: true,
}}
formName={props.formName}
/>
</TabPanel>
<TabPanel value={CUSTOM_ACTION_TABS.BODY}>
<GraphQLQueryContainer>
<Section isFullWidth withoutPadding>
<Zone layout="single_column">
<div className="t--graphql-query-editor">
<StyledFormLabel>Query</StyledFormLabel>
<DynamicTextField
border={CodeEditorBorder.ALL_SIDE}
dataTreePath={`${actionName}.config.${props.configProperty}.body`}
evaluatedPopUpLabel={"Query"}
mode={EditorModes.GRAPHQL_WITH_BINDING}
name={`${props.configProperty}.body`}
placeholder={`{{\n\tquery {\n\t\tname: inputName.property\n\t}\n}}`}
showLineNumbers
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INDENT}
theme={EditorTheme.LIGHT}
/>
</div>
</Zone>
<Zone layout="single_column">
<div className="t--graphql-variable-editor">
<StyledFormLabel>Query variables</StyledFormLabel>
<DynamicTextField
border={CodeEditorBorder.ALL_SIDE}
dataTreePath={`${actionName}.config.${props.configProperty}.variables`}
evaluatedPopUpLabel={"Query variables"}
expected={EXPECTED_VARIABLE}
height="100%"
mode={EditorModes.JSON_WITH_BINDING}
name={`${props.configProperty}.variables`}
placeholder={`${EXPECTED_VARIABLE.example}\n\n\\\\Take widget inputs using {{ }}`}
showLightningMenu={false}
showLineNumbers
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INDENT}
theme={EditorTheme.LIGHT}
/>
</div>
</Zone>
</Section>
</GraphQLQueryContainer>
</TabPanel>
</TabbedWrapper>
);
};

/**
* This component is used to configure the custom GraphQL actions for the external integration.
* It allows the user to add or update details for the custom GraphQL action like method type, path, headers, params, query, and variables.
*/
export class CustomGraphQLActionsControl extends BaseControl<ControlProps> {
getControlType(): ControlType {
return "CUSTOM_GRAPHQL_ACTIONS_CONFIG_FORM";
}
render() {
const { props } = this;

return (
<CustomActionFormLayout
configProperty={props.configProperty}
formName={props.formName}
methodOptions={GRAPHQL_HTTP_METHOD_OPTIONS.map((method) => ({
label: method.value,
value: method.value,
}))}
pathPlaceholder="/graphql"
>
<TabbedControls {...props} />
</CustomActionFormLayout>
);
}
}
Loading