fix: improve dropdown component (#8183)
Improved multipart form's dropdown component
This commit is contained in:
parent
82dc82633f
commit
803e5e7cc6
|
|
@ -56,5 +56,5 @@
|
||||||
"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')"
|
"multipartTypeDropdown":"button:contains('Type')"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ const StyledButtonWrapper = styled.div<ButtonWrapperProps>`
|
||||||
& > span {
|
& > span {
|
||||||
color: ${(props) => props.theme.colors.dropdown.header.text} !important;
|
color: ${(props) => props.theme.colors.dropdown.header.text} !important;
|
||||||
}
|
}
|
||||||
|
font-weight: ${(props) => props.theme.fontWeights[1]};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const StyledMenu = styled(Menu)<MenuComponentProps>`
|
const StyledMenu = styled(Menu)<MenuComponentProps>`
|
||||||
|
|
@ -58,14 +59,16 @@ const StyledMenuItem = styled(MenuItem)`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class DropdownComponent extends Component<DropdownComponentProps> {
|
// function checks if dropdown is connected to a redux form (of interface 'FormDropdownComponentProps')
|
||||||
componentDidMount() {
|
const isFormDropdown = (
|
||||||
const { input, options } = this.props;
|
props: DropdownComponentProps | FormDropdownComponentProps,
|
||||||
// set selected option to first option by default
|
): props is FormDropdownComponentProps => {
|
||||||
if (input && !input.value) {
|
return "input" in props && props.input !== undefined;
|
||||||
input.onChange(options[0].value);
|
};
|
||||||
}
|
|
||||||
}
|
class DropdownComponent extends Component<
|
||||||
|
DropdownComponentProps | FormDropdownComponentProps
|
||||||
|
> {
|
||||||
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;
|
||||||
|
|
@ -127,10 +130,13 @@ class DropdownComponent extends Component<DropdownComponentProps> {
|
||||||
option.label.toLowerCase().indexOf(query.toLowerCase()) > -1)
|
option.label.toLowerCase().indexOf(query.toLowerCase()) > -1)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
// function is called after user selects an option
|
||||||
onItemSelect = (item: DropdownOption): void => {
|
onItemSelect = (item: DropdownOption): void => {
|
||||||
const { input, selectHandler } = this.props;
|
if (isFormDropdown(this.props)) {
|
||||||
input && input.onChange(item.value);
|
this.props.input.onChange(item.value);
|
||||||
selectHandler && selectHandler(item.value);
|
} else {
|
||||||
|
this.props.selectHandler(item.value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderItem: ItemRenderer<DropdownOption> = (
|
renderItem: ItemRenderer<DropdownOption> = (
|
||||||
|
|
@ -152,37 +158,37 @@ class DropdownComponent extends Component<DropdownComponentProps> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
getDropdownOption = (value: string): DropdownOption | undefined => {
|
// helper function that returns a dropdown option given its value
|
||||||
|
// returns undefined if option isn't found
|
||||||
|
getDropdownOption = (
|
||||||
|
value: string | undefined,
|
||||||
|
): DropdownOption | undefined => {
|
||||||
return this.props.options.find((option) => option.value === value);
|
return this.props.options.find((option) => option.value === value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// this function returns the selected item's label
|
||||||
|
// returns the "placeholder" in the event that no option is selected.
|
||||||
getSelectedDisplayText = () => {
|
getSelectedDisplayText = () => {
|
||||||
const { input, selected } = this.props;
|
const value = isFormDropdown(this.props)
|
||||||
|
? this.props.input.value
|
||||||
|
: this.props.selected?.value;
|
||||||
|
|
||||||
if (input) {
|
const item = this.getDropdownOption(value);
|
||||||
const item = this.getDropdownOption(input.value);
|
return item ? item.label : this.props.placeholder;
|
||||||
return item && item.label;
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
const item = this.getDropdownOption(selected.value);
|
|
||||||
return item && item.label;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getActiveOption = (): DropdownOption => {
|
// this function returns the active option
|
||||||
const { input, options, selected } = this.props;
|
// returns undefined if no option is selected
|
||||||
const defaultActiveOption = options[0];
|
getActiveOption = (): DropdownOption | undefined => {
|
||||||
|
if (isFormDropdown(this.props)) {
|
||||||
if (input) {
|
return this.getDropdownOption(this.props.input.value);
|
||||||
return this.getDropdownOption(input.value) || defaultActiveOption;
|
|
||||||
} else {
|
} else {
|
||||||
return selected || defaultActiveOption;
|
return this.props.selected;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { autocomplete, height, input, options, width } = this.props;
|
const { autocomplete, height, options, width } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDropdown
|
<StyledDropdown
|
||||||
|
|
@ -196,7 +202,8 @@ class DropdownComponent extends Component<DropdownComponentProps> {
|
||||||
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}
|
// Destructure the "input" prop if dropdown is form-connected
|
||||||
|
{...(isFormDropdown(this.props) ? this.props.input : {})}
|
||||||
>
|
>
|
||||||
{this.props.toggle || (
|
{this.props.toggle || (
|
||||||
<StyledButtonWrapper height={height} width={width}>
|
<StyledButtonWrapper height={height} width={width}>
|
||||||
|
|
@ -212,23 +219,36 @@ class DropdownComponent extends Component<DropdownComponentProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DropdownComponentProps {
|
// Dropdown can either be connected to a redux-form
|
||||||
hasLabel?: boolean;
|
// or be a stand-alone component
|
||||||
options: DropdownOption[];
|
|
||||||
selectHandler?: (selectedValue: string) => void;
|
// Props common to both classes of dropdowns
|
||||||
selected?: DropdownOption;
|
export interface BaseDropdownComponentProps {
|
||||||
multiselectDisplayType?: "TAGS" | "CHECKBOXES";
|
|
||||||
checked?: boolean;
|
|
||||||
multi?: boolean;
|
|
||||||
autocomplete?: boolean;
|
|
||||||
addItem?: {
|
addItem?: {
|
||||||
displayText: string;
|
displayText: string;
|
||||||
addItemHandler: (name: string) => void;
|
addItemHandler: (name: string) => void;
|
||||||
};
|
};
|
||||||
toggle?: ReactNode;
|
autocomplete?: boolean;
|
||||||
input?: WrappedFieldInputProps;
|
checked?: boolean;
|
||||||
|
hasLabel?: boolean;
|
||||||
height?: string;
|
height?: string;
|
||||||
|
multi?: boolean;
|
||||||
|
multiselectDisplayType?: "TAGS" | "CHECKBOXES";
|
||||||
|
options: DropdownOption[];
|
||||||
|
placeholder: string;
|
||||||
|
toggle?: ReactNode;
|
||||||
width?: string;
|
width?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stand-alone dropdown interface
|
||||||
|
export interface DropdownComponentProps extends BaseDropdownComponentProps {
|
||||||
|
selectHandler: (selectedValue: string) => void;
|
||||||
|
selected: DropdownOption | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form-connected dropdown interface
|
||||||
|
export interface FormDropdownComponentProps extends BaseDropdownComponentProps {
|
||||||
|
input: WrappedFieldInputProps;
|
||||||
|
}
|
||||||
|
|
||||||
export default DropdownComponent;
|
export default DropdownComponent;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ interface DynamicDropdownFieldOptions {
|
||||||
options: DropdownOption[];
|
options: DropdownOption[];
|
||||||
height?: string;
|
height?: string;
|
||||||
width?: string;
|
width?: string;
|
||||||
|
placeholder: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DynamicDropdownFieldProps = BaseFieldProps & DynamicDropdownFieldOptions;
|
type DynamicDropdownFieldProps = BaseFieldProps & DynamicDropdownFieldOptions;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { Classes } from "components/ads/common";
|
||||||
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
|
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
|
||||||
import DynamicDropdownField from "./DynamicDropdownField";
|
import DynamicDropdownField from "./DynamicDropdownField";
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_MULTI_PART_DROPDOWN_PLACEHOLDER,
|
||||||
DEFAULT_MULTI_PART_DROPDOWN_WIDTH,
|
DEFAULT_MULTI_PART_DROPDOWN_WIDTH,
|
||||||
MULTI_PART_DROPDOWN_OPTIONS,
|
MULTI_PART_DROPDOWN_OPTIONS,
|
||||||
} from "constants/ApiEditorConstants";
|
} from "constants/ApiEditorConstants";
|
||||||
|
|
@ -187,6 +188,7 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
|
||||||
height="36px"
|
height="36px"
|
||||||
name={`${field}.type`}
|
name={`${field}.type`}
|
||||||
options={MULTI_PART_DROPDOWN_OPTIONS}
|
options={MULTI_PART_DROPDOWN_OPTIONS}
|
||||||
|
placeholder={DEFAULT_MULTI_PART_DROPDOWN_PLACEHOLDER}
|
||||||
width={DEFAULT_MULTI_PART_DROPDOWN_WIDTH}
|
width={DEFAULT_MULTI_PART_DROPDOWN_WIDTH}
|
||||||
/>
|
/>
|
||||||
</DynamicDropdownFieldWrapper>
|
</DynamicDropdownFieldWrapper>
|
||||||
|
|
|
||||||
|
|
@ -96,4 +96,5 @@ export const MULTI_PART_DROPDOWN_OPTIONS: MULTI_PART_DROPDOWN_OPTION[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_MULTI_PART_DROPDOWN_WIDTH = "75px";
|
export const DEFAULT_MULTI_PART_DROPDOWN_WIDTH = "77px";
|
||||||
|
export const DEFAULT_MULTI_PART_DROPDOWN_PLACEHOLDER = "Type";
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ function PostBodyData(props: Props) {
|
||||||
key={key}
|
key={key}
|
||||||
label=""
|
label=""
|
||||||
name="actionConfiguration.bodyFormData"
|
name="actionConfiguration.bodyFormData"
|
||||||
pushFields
|
// pushFields
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
@ -105,7 +105,7 @@ function PostBodyData(props: Props) {
|
||||||
key={key}
|
key={key}
|
||||||
label=""
|
label=""
|
||||||
name="actionConfiguration.bodyFormData"
|
name="actionConfiguration.bodyFormData"
|
||||||
pushFields
|
// pushFields
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,12 @@ export const transformRestAction = (data: ApiAction): ApiAction => {
|
||||||
return action;
|
return action;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filters empty key-value pairs or key-value-type(Multipart) from form data, headers and query params
|
||||||
function removeEmptyPairs(keyValueArray: any) {
|
function removeEmptyPairs(keyValueArray: any) {
|
||||||
if (!keyValueArray || !keyValueArray.length) return keyValueArray;
|
if (!keyValueArray || !keyValueArray.length) return keyValueArray;
|
||||||
return keyValueArray.filter(
|
return keyValueArray.filter(
|
||||||
(data: any) => data && (!isEmpty(data.key) || !isEmpty(data.value)),
|
(data: any) =>
|
||||||
|
data &&
|
||||||
|
(!isEmpty(data.key) || !isEmpty(data.value) || !isEmpty(data.type)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { transformRestAction } from "transformers/RestActionTransformer";
|
import { transformRestAction } from "transformers/RestActionTransformer";
|
||||||
import { PluginType, ApiAction } from "entities/Action";
|
import { PluginType, ApiAction } from "entities/Action";
|
||||||
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants";
|
import {
|
||||||
|
MultiPartOptionTypes,
|
||||||
|
POST_BODY_FORMAT_OPTIONS,
|
||||||
|
} from "constants/ApiEditorConstants";
|
||||||
|
|
||||||
// jest.mock("POST_");
|
// jest.mock("POST_");
|
||||||
|
|
||||||
|
|
@ -243,4 +246,62 @@ describe("Api action transformer", () => {
|
||||||
const result = transformRestAction(input);
|
const result = transformRestAction(input);
|
||||||
expect(result).toEqual(output);
|
expect(result).toEqual(output);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("filters empty pairs from form data", () => {
|
||||||
|
const input: ApiAction = {
|
||||||
|
...BASE_ACTION,
|
||||||
|
actionConfiguration: {
|
||||||
|
...BASE_ACTION.actionConfiguration,
|
||||||
|
httpMethod: "POST",
|
||||||
|
headers: [
|
||||||
|
{ key: "content-type", value: POST_BODY_FORMAT_OPTIONS[2].value },
|
||||||
|
],
|
||||||
|
body: "",
|
||||||
|
bodyFormData: [
|
||||||
|
{
|
||||||
|
key: "hey",
|
||||||
|
value: "ho",
|
||||||
|
type: MultiPartOptionTypes.TEXT,
|
||||||
|
editable: true,
|
||||||
|
mandatory: false,
|
||||||
|
description: "I been tryin to do it right",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
editable: true,
|
||||||
|
mandatory: false,
|
||||||
|
description: "I been tryin to do it right",
|
||||||
|
type: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// output object should not include the second bodyFormData object
|
||||||
|
// as its key, value and type are empty
|
||||||
|
const output: ApiAction = {
|
||||||
|
...BASE_ACTION,
|
||||||
|
actionConfiguration: {
|
||||||
|
...BASE_ACTION.actionConfiguration,
|
||||||
|
httpMethod: "POST",
|
||||||
|
headers: [
|
||||||
|
{ key: "content-type", value: POST_BODY_FORMAT_OPTIONS[2].value },
|
||||||
|
],
|
||||||
|
body: "",
|
||||||
|
bodyFormData: [
|
||||||
|
{
|
||||||
|
key: "hey",
|
||||||
|
value: "ho",
|
||||||
|
type: MultiPartOptionTypes.TEXT,
|
||||||
|
editable: true,
|
||||||
|
mandatory: false,
|
||||||
|
description: "I been tryin to do it right",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = transformRestAction(input);
|
||||||
|
expect(result).toEqual(output);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user