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:
parent
30c11e4a74
commit
f0c7ce0e89
|
|
@ -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": [
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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')"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user