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']",
|
||||
"paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.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 {
|
||||
color: ${(props) => props.theme.colors.dropdown.header.text} !important;
|
||||
}
|
||||
font-weight: ${(props) => props.theme.fontWeights[1]};
|
||||
}
|
||||
`;
|
||||
const StyledMenu = styled(Menu)<MenuComponentProps>`
|
||||
|
|
@ -58,14 +59,16 @@ const StyledMenuItem = styled(MenuItem)`
|
|||
}
|
||||
`;
|
||||
|
||||
class DropdownComponent extends Component<DropdownComponentProps> {
|
||||
componentDidMount() {
|
||||
const { input, options } = this.props;
|
||||
// set selected option to first option by default
|
||||
if (input && !input.value) {
|
||||
input.onChange(options[0].value);
|
||||
}
|
||||
}
|
||||
// function checks if dropdown is connected to a redux form (of interface 'FormDropdownComponentProps')
|
||||
const isFormDropdown = (
|
||||
props: DropdownComponentProps | FormDropdownComponentProps,
|
||||
): props is FormDropdownComponentProps => {
|
||||
return "input" in props && props.input !== undefined;
|
||||
};
|
||||
|
||||
class DropdownComponent extends Component<
|
||||
DropdownComponentProps | FormDropdownComponentProps
|
||||
> {
|
||||
private newItemTextInput: HTMLInputElement | null = null;
|
||||
private setNewItemTextInput = (element: HTMLInputElement | null) => {
|
||||
this.newItemTextInput = element;
|
||||
|
|
@ -127,10 +130,13 @@ class DropdownComponent extends Component<DropdownComponentProps> {
|
|||
option.label.toLowerCase().indexOf(query.toLowerCase()) > -1)
|
||||
);
|
||||
};
|
||||
// function is called after user selects an option
|
||||
onItemSelect = (item: DropdownOption): void => {
|
||||
const { input, selectHandler } = this.props;
|
||||
input && input.onChange(item.value);
|
||||
selectHandler && selectHandler(item.value);
|
||||
if (isFormDropdown(this.props)) {
|
||||
this.props.input.onChange(item.value);
|
||||
} else {
|
||||
this.props.selectHandler(item.value);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// this function returns the selected item's label
|
||||
// returns the "placeholder" in the event that no option is selected.
|
||||
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(input.value);
|
||||
return item && item.label;
|
||||
}
|
||||
if (selected) {
|
||||
const item = this.getDropdownOption(selected.value);
|
||||
return item && item.label;
|
||||
}
|
||||
return "";
|
||||
const item = this.getDropdownOption(value);
|
||||
return item ? item.label : this.props.placeholder;
|
||||
};
|
||||
|
||||
getActiveOption = (): DropdownOption => {
|
||||
const { input, options, selected } = this.props;
|
||||
const defaultActiveOption = options[0];
|
||||
|
||||
if (input) {
|
||||
return this.getDropdownOption(input.value) || defaultActiveOption;
|
||||
// this function returns the active option
|
||||
// returns undefined if no option is selected
|
||||
getActiveOption = (): DropdownOption | undefined => {
|
||||
if (isFormDropdown(this.props)) {
|
||||
return this.getDropdownOption(this.props.input.value);
|
||||
} else {
|
||||
return selected || defaultActiveOption;
|
||||
return this.props.selected;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { autocomplete, height, input, options, width } = this.props;
|
||||
const { autocomplete, height, options, width } = this.props;
|
||||
|
||||
return (
|
||||
<StyledDropdown
|
||||
|
|
@ -196,7 +202,8 @@ class DropdownComponent extends Component<DropdownComponentProps> {
|
|||
noResults={<MenuItem disabled text="No results." />}
|
||||
onItemSelect={this.onItemSelect}
|
||||
popoverProps={{ minimal: true }}
|
||||
{...input}
|
||||
// Destructure the "input" prop if dropdown is form-connected
|
||||
{...(isFormDropdown(this.props) ? this.props.input : {})}
|
||||
>
|
||||
{this.props.toggle || (
|
||||
<StyledButtonWrapper height={height} width={width}>
|
||||
|
|
@ -212,23 +219,36 @@ class DropdownComponent extends Component<DropdownComponentProps> {
|
|||
}
|
||||
}
|
||||
|
||||
export interface DropdownComponentProps {
|
||||
hasLabel?: boolean;
|
||||
options: DropdownOption[];
|
||||
selectHandler?: (selectedValue: string) => void;
|
||||
selected?: DropdownOption;
|
||||
multiselectDisplayType?: "TAGS" | "CHECKBOXES";
|
||||
checked?: boolean;
|
||||
multi?: boolean;
|
||||
autocomplete?: boolean;
|
||||
// Dropdown can either be connected to a redux-form
|
||||
// or be a stand-alone component
|
||||
|
||||
// Props common to both classes of dropdowns
|
||||
export interface BaseDropdownComponentProps {
|
||||
addItem?: {
|
||||
displayText: string;
|
||||
addItemHandler: (name: string) => void;
|
||||
};
|
||||
toggle?: ReactNode;
|
||||
input?: WrappedFieldInputProps;
|
||||
autocomplete?: boolean;
|
||||
checked?: boolean;
|
||||
hasLabel?: boolean;
|
||||
height?: string;
|
||||
multi?: boolean;
|
||||
multiselectDisplayType?: "TAGS" | "CHECKBOXES";
|
||||
options: DropdownOption[];
|
||||
placeholder: string;
|
||||
toggle?: ReactNode;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ interface DynamicDropdownFieldOptions {
|
|||
options: DropdownOption[];
|
||||
height?: string;
|
||||
width?: string;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
type DynamicDropdownFieldProps = BaseFieldProps & DynamicDropdownFieldOptions;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { Classes } from "components/ads/common";
|
|||
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
|
||||
import DynamicDropdownField from "./DynamicDropdownField";
|
||||
import {
|
||||
DEFAULT_MULTI_PART_DROPDOWN_PLACEHOLDER,
|
||||
DEFAULT_MULTI_PART_DROPDOWN_WIDTH,
|
||||
MULTI_PART_DROPDOWN_OPTIONS,
|
||||
} from "constants/ApiEditorConstants";
|
||||
|
|
@ -187,6 +188,7 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
|
|||
height="36px"
|
||||
name={`${field}.type`}
|
||||
options={MULTI_PART_DROPDOWN_OPTIONS}
|
||||
placeholder={DEFAULT_MULTI_PART_DROPDOWN_PLACEHOLDER}
|
||||
width={DEFAULT_MULTI_PART_DROPDOWN_WIDTH}
|
||||
/>
|
||||
</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}
|
||||
label=""
|
||||
name="actionConfiguration.bodyFormData"
|
||||
pushFields
|
||||
// pushFields
|
||||
theme={theme}
|
||||
/>
|
||||
),
|
||||
|
|
@ -105,7 +105,7 @@ function PostBodyData(props: Props) {
|
|||
key={key}
|
||||
label=""
|
||||
name="actionConfiguration.bodyFormData"
|
||||
pushFields
|
||||
// pushFields
|
||||
theme={theme}
|
||||
/>
|
||||
),
|
||||
|
|
|
|||
|
|
@ -56,9 +56,12 @@ export const transformRestAction = (data: ApiAction): ApiAction => {
|
|||
return action;
|
||||
};
|
||||
|
||||
// Filters empty key-value pairs or key-value-type(Multipart) from form data, headers and query params
|
||||
function removeEmptyPairs(keyValueArray: any) {
|
||||
if (!keyValueArray || !keyValueArray.length) return keyValueArray;
|
||||
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 { 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_");
|
||||
|
||||
|
|
@ -243,4 +246,62 @@ describe("Api action transformer", () => {
|
|||
const result = transformRestAction(input);
|
||||
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