Feature/option to upload file or text in multipart form (#6534)

* added option to multipart form data for file or text upload

* updated styles for the dynamic-text-field-with-dropdown styled component

* added cypress tests for multipart body type

* code refactor: moved constants to ApiEditorConstants.ts

* updated multipart dropdown styles

* minor bug fix
This commit is contained in:
Favour Ohanekwu 2021-08-16 13:00:11 +01:00 committed by GitHub
parent 30c11e4a74
commit f0c7ce0e89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 353 additions and 108 deletions

View File

@ -43,6 +43,7 @@
"next": "?page=2&pageSize=10", "next": "?page=2&pageSize=10",
"prev": "?page=1&pageSize=10", "prev": "?page=1&pageSize=10",
"apiFormDataBodyType": "x-www-form-urlencoded", "apiFormDataBodyType": "x-www-form-urlencoded",
"apiMultipartBodyType": "multi-part",
"defaultMoustacheData": "{{Input1.text", "defaultMoustacheData": "{{Input1.text",
"defaultInputWidget": "{{Table1.selectedRow.id", "defaultInputWidget": "{{Table1.selectedRow.id",
"deafultDropDownWidget": [ "deafultDropDownWidget": [

View File

@ -0,0 +1,20 @@
const testdata = require("../../../../fixtures/testdata.json");
const apiEditor = require("../../../../locators/ApiEditor.json");
const apiwidget = require("../../../../locators/apiWidgetslocator.json");
describe("API Panel request body", function() {
it("Check whether input and type dropdown selector exist when multi-part is selected", function() {
cy.NavigateToAPI_Panel();
cy.CreateAPI("FirstAPI");
cy.SelectAction(testdata.postAction);
cy.contains(apiEditor.bodyTab).click();
cy.contains(testdata.apiFormDataBodyType).click();
cy.contains(testdata.apiMultipartBodyType).click();
cy.get(apiwidget.formEncoded).should("be.visible");
cy.get(apiwidget.multipartTypeDropdown).should("be.visible");
cy.DeleteAPI();
});
});

View File

@ -55,5 +55,6 @@
"renameEntity": ".single-select >div:contains('Edit Name')", "renameEntity": ".single-select >div:contains('Edit Name')",
"paramsTab": "//li//span[text()='Params']", "paramsTab": "//li//span[text()='Params']",
"paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0", "paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0",
"paramValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0" "paramValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0",
"multipartTypeDropdown":"button:contains('Text')"
} }

View File

@ -21,7 +21,25 @@ import { Toaster } from "components/ads/Toast";
import ReCAPTCHA from "react-google-recaptcha"; import ReCAPTCHA from "react-google-recaptcha";
const getButtonColorStyles = (props: { theme: Theme } & ButtonStyleProps) => { const getButtonColorStyles = (props: { theme: Theme } & ButtonStyleProps) => {
if (props.filled) return props.theme.colors.textOnDarkBG; if (props.filled) {
return props.accent === "grey"
? props.theme.colors.textOnGreyBG
: props.theme.colors.textOnDarkBG;
}
if (props.accent) {
if (props.accent === "secondary") {
return props.theme.colors[AccentColorMap["primary"]];
}
return props.theme.colors[AccentColorMap[props.accent]];
}
};
const getButtonFillStyles = (props: { theme: Theme } & ButtonStyleProps) => {
if (props.filled) {
return props.accent === "grey"
? props.theme.colors.dropdownIconDarkBg
: props.theme.colors.textOnDarkBG;
}
if (props.accent) { if (props.accent) {
if (props.accent === "secondary") { if (props.accent === "secondary") {
return props.theme.colors[AccentColorMap["primary"]]; return props.theme.colors[AccentColorMap["primary"]];
@ -33,7 +51,7 @@ const getButtonColorStyles = (props: { theme: Theme } & ButtonStyleProps) => {
const ButtonColorStyles = css<ButtonStyleProps>` const ButtonColorStyles = css<ButtonStyleProps>`
color: ${getButtonColorStyles}; color: ${getButtonColorStyles};
svg { svg {
fill: ${getButtonColorStyles}; fill: ${getButtonFillStyles};
} }
`; `;
@ -48,6 +66,7 @@ const AccentColorMap: Record<ButtonStyleName, string> = {
primary: "primaryOld", primary: "primaryOld",
secondary: "secondaryOld", secondary: "secondaryOld",
error: "error", error: "error",
grey: "dropdownGreyBg",
}; };
const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => ( const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => (
@ -67,9 +86,14 @@ const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => (
props.accent props.accent
? props.theme.colors[AccentColorMap[props.accent]] ? props.theme.colors[AccentColorMap[props.accent]]
: props.theme.colors.primary}; : props.theme.colors.primary};
color: ${(props) =>
props.accent === "grey"
? props.theme.colors.textOnGreyBG
: props.theme.colors.textOnDarkBG};
border-radius: 0; border-radius: 0;
font-weight: ${(props) => props.theme.fontWeights[2]}; font-weight: ${(props) => props.theme.fontWeights[2]};
outline: none; outline: none;
&.bp3-button { &.bp3-button {
padding: 0px 10px; padding: 0px 10px;
} }
@ -80,10 +104,10 @@ const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => (
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
max-height: 100%; max-height: 100%;
overflow: hidden; overflow: hidden;
} }
&&:hover, &&:hover,
&&:focus { &&:focus {
${ButtonColorStyles}; ${ButtonColorStyles};
@ -122,7 +146,7 @@ const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => (
} }
`; `;
export type ButtonStyleName = "primary" | "secondary" | "error"; export type ButtonStyleName = "primary" | "secondary" | "error" | "grey";
type ButtonStyleProps = { type ButtonStyleProps = {
accent?: ButtonStyleName; accent?: ButtonStyleName;

View File

@ -1,7 +1,16 @@
import React, { Component, ReactNode } from "react"; import React, { Component, ReactNode } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { MenuItem, Menu, ControlGroup, InputGroup } from "@blueprintjs/core"; import {
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; MenuItem,
Menu,
ControlGroup,
InputGroup,
IMenuProps,
} from "@blueprintjs/core";
import {
BaseButton,
ButtonStyleName,
} from "components/designSystems/blueprint/ButtonComponent";
import { import {
ItemRenderer, ItemRenderer,
Select, Select,
@ -9,11 +18,40 @@ import {
IItemListRendererProps, IItemListRendererProps,
} from "@blueprintjs/select"; } from "@blueprintjs/select";
import { DropdownOption } from "widgets/DropdownWidget"; import { DropdownOption } from "widgets/DropdownWidget";
import { WrappedFieldInputProps } from "redux-form";
interface ButtonWrapperProps {
width?: string;
}
interface MenuProps {
width?: string;
}
type MenuComponentProps = IMenuProps & MenuProps;
const Dropdown = Select.ofType<DropdownOption>(); const Dropdown = Select.ofType<DropdownOption>();
const StyledDropdown = styled(Dropdown)``; const StyledDropdown = styled(Dropdown)``;
const StyledButtonWrapper = styled.div<ButtonWrapperProps>`
width: ${(props) => props.width || "100%"};
`;
const StyledMenu = styled(Menu)<MenuComponentProps>`
min-width: ${(props) => props.width || "100%"};
border-radius: 0;
`;
const StyledMenuItem = styled(MenuItem)`
border-radius: 0;
&&&.bp3-active {
background: ${(props) => props.theme.colors.propertyPane.activeButtonText};
}
`;
class DropdownComponent extends Component<DropdownComponentProps> { class DropdownComponent extends Component<DropdownComponentProps> {
componentDidMount() {
const { input, selected } = this.props;
input && input.onChange(selected?.value);
}
private newItemTextInput: HTMLInputElement | null = null; private newItemTextInput: HTMLInputElement | null = null;
private setNewItemTextInput = (element: HTMLInputElement | null) => { private setNewItemTextInput = (element: HTMLInputElement | null) => {
this.newItemTextInput = element; this.newItemTextInput = element;
@ -41,36 +79,35 @@ class DropdownComponent extends Component<DropdownComponentProps> {
renderItemList: ItemListRenderer<DropdownOption> = ( renderItemList: ItemListRenderer<DropdownOption> = (
props: IItemListRendererProps<DropdownOption>, props: IItemListRendererProps<DropdownOption>,
) => { ) => {
if (this.props.addItem) { const { items, renderItem } = props;
const renderItems = props.items.map(props.renderItem).filter(Boolean); const { addItem, width } = this.props;
const displayMode = ( const renderItems = items.map(renderItem).filter(Boolean);
<BaseButton
accent="primary"
filled
icon-right="plus"
onClick={this.showTextBox}
text={this.props.addItem.displayText}
/>
);
const editMode = (
<ControlGroup fill>
<InputGroup inputRef={this.setNewItemTextInput} />
<BaseButton
filled
onClick={this.handleAddItem}
text={this.props.addItem.displayText}
/>
</ControlGroup>
);
return (
<Menu ulRef={props.itemsParentRef}>
{renderItems}
{!this.state.isEditing ? displayMode : editMode}
</Menu>
);
}
return null; const displayMode = (
<BaseButton
accent="primary"
filled
icon-right="plus"
onClick={this.showTextBox}
text={addItem?.displayText}
/>
);
const editMode = (
<ControlGroup fill>
<InputGroup inputRef={this.setNewItemTextInput} />
<BaseButton
filled
onClick={this.handleAddItem}
text={addItem?.displayText}
/>
</ControlGroup>
);
return (
<StyledMenu ulRef={props.itemsParentRef} width={width}>
{renderItems}
{addItem && (!this.state.isEditing ? displayMode : editMode)}
</StyledMenu>
);
}; };
searchItem = (query: string, option: DropdownOption): boolean => { searchItem = (query: string, option: DropdownOption): boolean => {
@ -82,6 +119,7 @@ class DropdownComponent extends Component<DropdownComponentProps> {
); );
}; };
onItemSelect = (item: DropdownOption): void => { onItemSelect = (item: DropdownOption): void => {
this.props.input?.onChange(item.value);
this.props.selectHandler(item.value); this.props.selectHandler(item.value);
}; };
@ -93,13 +131,13 @@ class DropdownComponent extends Component<DropdownComponentProps> {
return null; return null;
} }
return ( return (
<MenuItem <StyledMenuItem
active={modifiers.active} active={modifiers.active}
key={option.value} key={option.value}
label={option.label ? option.label : ""} label={this.props.hasLabel ? option.label : ""}
onClick={handleClick} onClick={handleClick}
shouldDismissPopover={false} shouldDismissPopover={false}
text={option.label || option.label} text={option.label}
/> />
); );
}; };
@ -110,31 +148,45 @@ class DropdownComponent extends Component<DropdownComponentProps> {
(option) => option.value === selectedValue, (option) => option.value === selectedValue,
); );
return item && (item.label || item.label); return item && item.label;
} }
return ""; return "";
}; };
render() { render() {
const {
accent,
autocomplete,
filled,
input,
options,
selected,
width,
} = this.props;
return ( return (
<StyledDropdown <StyledDropdown
activeItem={this.props.selected} activeItem={selected}
filterable={!!this.props.autocomplete} filterable={!!autocomplete}
itemListRenderer={this.props.addItem && this.renderItemList} itemListRenderer={this.renderItemList}
itemPredicate={this.searchItem} itemPredicate={this.searchItem}
itemRenderer={this.renderItem} itemRenderer={this.renderItem}
items={this.props.options} items={options}
itemsEqual="value" itemsEqual="value"
noResults={<MenuItem disabled text="No results." />} noResults={<MenuItem disabled text="No results." />}
onItemSelect={this.onItemSelect} onItemSelect={this.onItemSelect}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
{...input}
> >
{this.props.toggle || ( {this.props.toggle || (
<BaseButton <StyledButtonWrapper width={width}>
accent="secondary" <BaseButton
rightIcon="chevron-down" accent={accent || "secondary"}
text={this.getSelectedDisplayText()} filled={!!filled}
/> rightIcon="chevron-down"
text={this.getSelectedDisplayText()}
/>
</StyledButtonWrapper>
)} )}
</StyledDropdown> </StyledDropdown>
); );
@ -142,6 +194,7 @@ class DropdownComponent extends Component<DropdownComponentProps> {
} }
export interface DropdownComponentProps { export interface DropdownComponentProps {
hasLabel?: boolean;
options: DropdownOption[]; options: DropdownOption[];
selectHandler: (selectedValue: string) => void; selectHandler: (selectedValue: string) => void;
selected?: DropdownOption; selected?: DropdownOption;
@ -154,6 +207,10 @@ export interface DropdownComponentProps {
addItemHandler: (name: string) => void; addItemHandler: (name: string) => void;
}; };
toggle?: ReactNode; toggle?: ReactNode;
accent?: ButtonStyleName;
filled?: boolean;
input?: WrappedFieldInputProps;
width?: string;
} }
export default DropdownComponent; export default DropdownComponent;

View File

@ -0,0 +1,50 @@
import React from "react";
import { Field, BaseFieldProps } from "redux-form";
import DropdownComponent from "components/editorComponents/DropdownComponent";
import { ButtonStyleName } from "components/designSystems/blueprint/ButtonComponent";
import { DropdownOption } from "widgets/DropdownWidget";
interface DynamicDropdownFieldOptions {
options: DropdownOption[];
accent?: ButtonStyleName;
filled?: boolean;
width?: string;
}
type DynamicDropdownFieldProps = BaseFieldProps & DynamicDropdownFieldOptions;
class DynamicDropdownField extends React.Component<
DynamicDropdownFieldProps,
{
selectedOption: DropdownOption;
}
> {
constructor(props: DynamicDropdownFieldProps) {
super(props);
this.state = {
selectedOption: this.props.options[0],
};
}
handleOptionSelection = (selectedValue: string): void => {
const selectedOption = this.props.options.find(
(option) => option.value === selectedValue,
) as DropdownOption;
this.setState({
selectedOption,
});
};
render() {
const dropdownProps = {
selectHandler: this.handleOptionSelection,
selected: this.state.selectedOption,
};
return (
<Field component={DropdownComponent} {...this.props} {...dropdownProps} />
);
}
}
export default DynamicDropdownField;

View File

@ -30,6 +30,7 @@ class DynamicTextField extends React.Component<
theme: this.props.theme || EditorTheme.LIGHT, theme: this.props.theme || EditorTheme.LIGHT,
size: this.props.size || EditorSize.COMPACT, size: this.props.size || EditorSize.COMPACT,
}; };
return <Field component={CodeEditor} {...this.props} {...editorProps} />; return <Field component={CodeEditor} {...this.props} {...editorProps} />;
} }
} }

View File

@ -14,6 +14,12 @@ import {
import Text, { Case, TextType } from "components/ads/Text"; import Text, { Case, TextType } from "components/ads/Text";
import { Classes } from "components/ads/common"; import { Classes } from "components/ads/common";
import { AutocompleteDataType } from "utils/autocomplete/TernServer"; import { AutocompleteDataType } from "utils/autocomplete/TernServer";
import DynamicDropdownField from "./DynamicDropdownField";
import { Colors } from "constants/Colors";
import {
DEFAULT_MULTI_PART_DROPDOWN_WIDTH,
MULTI_PART_DROPDOWN_OPTIONS,
} from "constants/ApiEditorConstants";
type CustomStack = { type CustomStack = {
removeTopPadding?: boolean; removeTopPadding?: boolean;
@ -83,6 +89,23 @@ const FlexContainer = styled.div`
} }
`; `;
const DynamicTextFieldWithDropdownWrapper = styled.div`
display: flex;
position: relative;
border-bottom: solid 1px ${Colors.MERCURY};
margin-bottom: 10px;
top: -2px;
& .CodeEditorTarget * {
border-bottom: none !important;
}
`;
const DynamicDropdownFieldWrapper = styled.div`
position: relative;
top: 1px;
margin-left: 5px;
`;
const expected = { const expected = {
type: FIELD_VALUES.API_ACTION.params, type: FIELD_VALUES.API_ACTION.params,
example: "", example: "",
@ -136,16 +159,41 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
return ( return (
<FormRowWithLabel key={index}> <FormRowWithLabel key={index}>
<Flex size={1}> <Flex size={1}>
<DynamicTextField {props.hasType ? (
border={CodeEditorBorder.BOTTOM_SIDE} <DynamicTextFieldWithDropdownWrapper>
className={`t--${field}.key.${index}`} <DynamicTextField
dataTreePath={`${props.dataTreePath}[${index}].key`} border={CodeEditorBorder.BOTTOM_SIDE}
expected={expected} className={`t--${field}.key.${index}`}
hoverInteraction dataTreePath={`${props.dataTreePath}[${index}].key`}
name={`${field}.key`} expected={expected}
placeholder={`Key ${index + 1}`} hoverInteraction
theme={props.theme} name={`${field}.key`}
/> placeholder={`Key ${index + 1}`}
theme={props.theme}
/>
<DynamicDropdownFieldWrapper>
<DynamicDropdownField
accent="grey"
filled
name={`${field}.type`}
options={MULTI_PART_DROPDOWN_OPTIONS}
width={DEFAULT_MULTI_PART_DROPDOWN_WIDTH}
/>
</DynamicDropdownFieldWrapper>
</DynamicTextFieldWithDropdownWrapper>
) : (
<DynamicTextField
border={CodeEditorBorder.BOTTOM_SIDE}
className={`t--${field}.key.${index}`}
dataTreePath={`${props.dataTreePath}[${index}].key`}
expected={expected}
hoverInteraction
name={`${field}.key`}
placeholder={`Key ${index + 1}`}
theme={props.theme}
/>
)}
</Flex> </Flex>
{!props.actionConfig && ( {!props.actionConfig && (
@ -239,6 +287,7 @@ type Props = {
dataTreePath?: string; dataTreePath?: string;
hideHeader?: boolean; hideHeader?: boolean;
theme?: EditorTheme; theme?: EditorTheme;
hasType?: boolean;
}; };
function KeyValueFieldArray(props: Props) { function KeyValueFieldArray(props: Props) {

View File

@ -48,7 +48,7 @@ export const CONTENT_TYPE_HEADER_KEY = "content-type";
export enum ApiContentTypes { export enum ApiContentTypes {
JSON = "json", JSON = "json",
FORM_URLENCODED = "x-www-form-urlencoded", FORM_URLENCODED = "x-www-form-urlencoded",
MULTIPART_FORM_DATA = "form-data", MULTIPART_FORM_DATA = "multi-part",
RAW = "raw", RAW = "raw",
} }
@ -74,3 +74,26 @@ export const POST_BODY_FORMAT_TITLES = POST_BODY_FORMAT_OPTIONS.map(
return { title: option.label, key: option.value }; return { title: option.label, key: option.value };
}, },
); );
export enum MultiPartOptionTypes {
TEXT = "Text",
FILE = "File",
}
export interface MULTI_PART_DROPDOWN_OPTION {
label: MultiPartOptionTypes;
value: string;
}
export const MULTI_PART_DROPDOWN_OPTIONS: MULTI_PART_DROPDOWN_OPTION[] = [
{
label: MultiPartOptionTypes.TEXT,
value: "TEXT",
},
{
label: MultiPartOptionTypes.FILE,
value: "FILE",
},
];
export const DEFAULT_MULTI_PART_DROPDOWN_WIDTH = "75px";

View File

@ -2706,6 +2706,7 @@ export const theme: Theme = {
inputInactiveBG: Colors.AQUA_HAZE, inputInactiveBG: Colors.AQUA_HAZE,
textDefault: Colors.BLACK_PEARL, textDefault: Colors.BLACK_PEARL,
textOnDarkBG: Colors.WHITE, textOnDarkBG: Colors.WHITE,
textOnGreyBG: Colors.CHARCOAL,
textAnchor: Colors.PURPLE, textAnchor: Colors.PURPLE,
border: Colors.GEYSER, border: Colors.GEYSER,
paneCard: Colors.SHARK, paneCard: Colors.SHARK,
@ -2748,6 +2749,8 @@ export const theme: Theme = {
dropdownIconBg: Colors.ALTO2, dropdownIconBg: Colors.ALTO2,
welcomeTourStickySidebarColor: Colors.WHITE, welcomeTourStickySidebarColor: Colors.WHITE,
welcomeTourStickySidebarBackground: "#F86A2B", welcomeTourStickySidebarBackground: "#F86A2B",
dropdownIconDarkBg: Colors.DARK_GRAY,
dropdownGreyBg: Colors.Gallery,
}, },
lineHeights: [0, 14, 16, 18, 22, 24, 28, 36, 48, 64, 80], lineHeights: [0, 14, 16, 18, 22, 24, 28, 36, 48, 64, 80],

View File

@ -63,6 +63,65 @@ function PostBodyData(props: Props) {
updateBodyContentType, updateBodyContentType,
} = props; } = props;
const tabComponentsMap = (
key: string,
contentType: ApiContentTypes,
): JSX.Element => {
return {
[ApiContentTypes.JSON]: (
<JSONEditorFieldWrapper className={"t--apiFormPostBody"} key={key}>
<DynamicTextField
dataTreePath={`${dataTreePath}.body`}
expected={expectedPostBody}
mode={EditorModes.JSON_WITH_BINDING}
name="actionConfiguration.body"
placeholder={
'{\n "name":"{{ inputName.property }}",\n "preference":"{{ dropdownName.property }}"\n}\n\n\\\\Take widget inputs using {{ }}'
}
showLineNumbers
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INDENT}
theme={theme}
/>
</JSONEditorFieldWrapper>
),
[ApiContentTypes.FORM_URLENCODED]: (
<KeyValueFieldArray
dataTreePath={`${dataTreePath}.bodyFormData`}
key={key}
label=""
name="actionConfiguration.bodyFormData"
theme={theme}
/>
),
[ApiContentTypes.MULTIPART_FORM_DATA]: (
<KeyValueFieldArray
dataTreePath={`${dataTreePath}.bodyFormData`}
hasType
key={key}
label=""
name="actionConfiguration.bodyFormData"
pushFields
theme={theme}
/>
),
[ApiContentTypes.RAW]: (
<JSONEditorFieldWrapper key={key}>
<DynamicTextField
dataTreePath={`${dataTreePath}.body`}
mode={EditorModes.TEXT_WITH_BINDING}
name="actionConfiguration.body"
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INDENT}
theme={theme}
/>
</JSONEditorFieldWrapper>
),
}[contentType];
};
return ( return (
<PostBodyContainer> <PostBodyContainer>
<MultiSwitch <MultiSwitch
@ -71,54 +130,11 @@ function PostBodyData(props: Props) {
} }
selected={displayFormat} selected={displayFormat}
tabs={POST_BODY_FORMAT_TITLES.map((el) => { tabs={POST_BODY_FORMAT_TITLES.map((el) => {
let component = ( return {
<JSONEditorFieldWrapper key: el.key,
className={"t--apiFormPostBody"} title: el.title,
key={el.key} panelComponent: tabComponentsMap(el.key, el.title),
> };
<DynamicTextField
dataTreePath={`${dataTreePath}.body`}
expected={expectedPostBody}
mode={EditorModes.JSON_WITH_BINDING}
name="actionConfiguration.body"
placeholder={
'{\n "name":"{{ inputName.property }}",\n "preference":"{{ dropdownName.property }}"\n}\n\n\\\\Take widget inputs using {{ }}'
}
showLineNumbers
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INDENT}
theme={theme}
/>
</JSONEditorFieldWrapper>
);
if (
el.title === ApiContentTypes.FORM_URLENCODED ||
el.title === ApiContentTypes.MULTIPART_FORM_DATA
) {
component = (
<KeyValueFieldArray
dataTreePath={`${dataTreePath}.bodyFormData`}
key={el.key}
label=""
name="actionConfiguration.bodyFormData"
theme={theme}
/>
);
} else if (el.title === ApiContentTypes.RAW) {
component = (
<JSONEditorFieldWrapper key={el.key}>
<DynamicTextField
dataTreePath={`${dataTreePath}.body`}
mode={EditorModes.TEXT_WITH_BINDING}
name="actionConfiguration.body"
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INDENT}
theme={theme}
/>
</JSONEditorFieldWrapper>
);
}
return { key: el.key, title: el.title, panelComponent: component };
})} })}
/> />
</PostBodyContainer> </PostBodyContainer>