Skip to content

Commit

Permalink
🪟 🎉 [Connector Builder] Add request options component (#20497)
Browse files Browse the repository at this point in the history
* style view titles

* save progress on adding stream delete functionality

* fix delete behavior

* remove remaining console logs

* consolidate shared styles into BuilderConfigView component

* add key value list field for request options

* fix styles

* add hover color for remove button

* fix size of remove button

* update tooltip to be more accurate

* remove console logs

* adjust colors of key value component
  • Loading branch information
lmossman committed Dec 15, 2022
1 parent bc5b72d commit df7dd51
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

.jobStartFailure {
padding: variables.$spacing-lg;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export const AddStreamButton: React.FC<AddStreamButtonProps> = ({ onAddStream })
urlPath: values.urlPath,
fieldPointer: [],
httpMethod: "GET",
requestOptions: {
requestParameters: [],
requestHeaders: [],
requestBody: [],
},
},
]);
setIsOpen(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import classNames from "classnames";
import React from "react";

import { Card } from "components/ui/Card";

import styles from "./BuilderCard.module.scss";

export const BuilderCard: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
return <Card className={styles.card}>{children}</Card>;
interface BuilderCardProps {
className?: string;
}

export const BuilderCard: React.FC<React.PropsWithChildren<BuilderCardProps>> = ({ children, className }) => {
return <Card className={classNames(className, styles.card)}>{children}</Card>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ interface BaseFieldProps {
optional?: boolean;
}

type BuilderFieldProps = BaseFieldProps & ({ type: "text" } | { type: "array" } | { type: "enum"; options: string[] });
type BuilderFieldProps = BaseFieldProps & ({ type: "text" | "array" } | { type: "enum"; options: string[] });

const EnumField: React.FC<EnumFieldProps> = ({ options, value, setValue, error, ...props }) => {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}

.label {
color: colors.$grey;
color: colors.$grey-400;
}

.inputContainer {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@use "scss/variables";

.content {
margin-top: variables.$spacing-lg;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BuilderCard } from "./BuilderCard";
import { BuilderConfigView } from "./BuilderConfigView";
import { BuilderField } from "./BuilderField";
import { BuilderTitle } from "./BuilderTitle";
import styles from "./GlobalConfigView.module.scss";

export const GlobalConfigView: React.FC = () => {
const { formatMessage } = useIntl();
Expand All @@ -12,7 +13,7 @@ export const GlobalConfigView: React.FC = () => {
<BuilderConfigView heading={formatMessage({ id: "connectorBuilder.globalConfiguration" })}>
{/* Not using intl for the labels and tooltips in this component in order to keep maintainence simple */}
<BuilderTitle path="global.connectorName" label="Connector Name" size="lg" />
<BuilderCard>
<BuilderCard className={styles.content}>
<BuilderField type="text" path="global.urlBase" label="API URL" tooltip="Base URL of the source API" />
</BuilderCard>
</BuilderConfigView>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@use "scss/variables";
@use "scss/colors";

$removeButtonWidth: 20px;

.inputContainer {
width: 100%;
display: flex;
gap: variables.$spacing-xl;
align-items: center;
}

.labeledInput {
flex: 1;
max-width: 400px;
display: flex;
gap: variables.$spacing-sm;
align-items: center;
}

.kvLabel {
color: colors.$grey-400;
}

.removeButton {
border: none;
background-color: transparent;
color: colors.$grey;
cursor: pointer;
padding: 0;
width: $removeButtonWidth;
height: $removeButtonWidth;
font-size: 18px;
transition: variables.$transition;

&:hover {
color: colors.$red;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useField } from "formik";
import { FormattedMessage } from "react-intl";

import GroupControls from "components/GroupControls";
import { ControlLabels } from "components/LabeledControl";
import { Button } from "components/ui/Button";
import { Input } from "components/ui/Input";
import { Text } from "components/ui/Text";

import styles from "./KeyValueListField.module.scss";

interface KeyValueInputProps {
keyValue: [string, string];
onChange: (keyValue: [string, string]) => void;
onRemove: () => void;
}

const KeyValueInput: React.FC<KeyValueInputProps> = ({ keyValue, onChange, onRemove }) => {
return (
<div className={styles.inputContainer}>
<div className={styles.labeledInput}>
<Text className={styles.kvLabel}>
<FormattedMessage id="connectorBuilder.key" />
</Text>
<Input value={keyValue[0]} onChange={(e) => onChange([e.target.value, keyValue[1]])} />
</div>
<div className={styles.labeledInput}>
<Text className={styles.kvLabel}>
<FormattedMessage id="connectorBuilder.value" />
</Text>
<Input value={keyValue[1]} onChange={(e) => onChange([keyValue[0], e.target.value])} />
</div>
<button type="button" className={styles.removeButton} onClick={onRemove}>
<FontAwesomeIcon icon={faXmark} size="1x" />
</button>
</div>
);
};

interface KeyValueListFieldProps {
path: string;
label: string;
tooltip: string;
}

export const KeyValueListField: React.FC<KeyValueListFieldProps> = ({ path, label, tooltip }) => {
const [{ value: keyValueList }, , { setValue: setKeyValueList }] = useField<Array<[string, string]>>(path);

return (
<GroupControls label={<ControlLabels label={label} infoTooltipContent={tooltip} />}>
{keyValueList.map((keyValue, keyValueIndex) => (
<KeyValueInput
key={keyValueIndex}
keyValue={keyValue}
onChange={(newKeyValue) => {
const updatedList = keyValueList.map((entry, index) => (index === keyValueIndex ? newKeyValue : entry));
setKeyValueList(updatedList);
}}
onRemove={() => {
const updatedList = keyValueList.filter((_, index) => index !== keyValueIndex);
setKeyValueList(updatedList);
}}
/>
))}
<div>
<Button variant="secondary" onClick={() => setKeyValueList([...keyValueList, ["", ""]])}>
<FormattedMessage id="connectorBuilder.addKeyValue" />
</Button>
</div>
</GroupControls>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ $deleteButtonWidth: 24px;
.deleteButton {
border: none;
background-color: transparent;
color: colors.$grey;
color: colors.$grey-400;
border-radius: variables.$border-radius-xs;
width: $deleteButtonWidth;
height: $deleteButtonWidth;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { BuilderCard } from "./BuilderCard";
import { BuilderConfigView } from "./BuilderConfigView";
import { BuilderField } from "./BuilderField";
import { BuilderTitle } from "./BuilderTitle";
import { KeyValueListField } from "./KeyValueListField";
import styles from "./StreamConfigView.module.scss";

interface StreamConfigViewProps {
Expand Down Expand Up @@ -73,6 +74,23 @@ export const StreamConfigView: React.FC<StreamConfigViewProps> = ({ streamNum })
tooltip="Pointer into the response that should be extracted as the final record"
/>
</BuilderCard>
<BuilderCard>
<KeyValueListField
path={streamFieldPath("requestOptions.requestParameters")}
label="Request Parameters"
tooltip="Parameters to attach to API requests"
/>
<KeyValueListField
path={streamFieldPath("requestOptions.requestHeaders")}
label="Request Headers"
tooltip="Headers to attach to API requests"
/>
<KeyValueListField
path={streamFieldPath("requestOptions.requestBody")}
label="Request Body"
tooltip="Body to attach to API requests as url-encoded form values"
/>
</BuilderCard>
</BuilderConfigView>
);
};
15 changes: 15 additions & 0 deletions airbyte-webapp/src/components/connectorBuilder/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export interface BuilderStream {
urlPath: string;
fieldPointer: string[];
httpMethod: "GET" | "POST";
requestOptions: {
requestParameters: Array<[string, string]>;
requestHeaders: Array<[string, string]>;
requestBody: Array<[string, string]>;
};
}

export const builderFormValidationSchema = yup.object().shape({
Expand All @@ -28,6 +33,11 @@ export const builderFormValidationSchema = yup.object().shape({
urlPath: yup.string().required("form.empty.error"),
fieldPointer: yup.array().of(yup.string()),
httpMethod: yup.mixed().oneOf(["GET", "POST"]),
requestOptions: yup.object().shape({
requestParameters: yup.array().of(yup.array().of(yup.string())),
requestHeaders: yup.array().of(yup.array().of(yup.string())),
requestBody: yup.array().of(yup.array().of(yup.string())),
}),
})
),
});
Expand All @@ -42,6 +52,11 @@ export const convertToManifest = (values: BuilderFormValues): ConnectorManifest
name: stream.name,
url_base: values.global?.urlBase,
path: stream.urlPath,
request_options_provider: {
request_parameters: Object.fromEntries(stream.requestOptions.requestParameters),
request_headers: Object.fromEntries(stream.requestOptions.requestHeaders),
request_body_data: Object.fromEntries(stream.requestOptions.requestBody),
},
// TODO: remove these empty "config" values once they are no longer required in the connector manifest JSON schema
config: {},
},
Expand Down
3 changes: 3 additions & 0 deletions airbyte-webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@
"connectorBuilder.uiYamlToggle.yaml": "YAML",
"connectorBuilder.resetAll": "Reset all",
"connectorBuilder.emptyName": "(empty)",
"connectorBuilder.key": "key",
"connectorBuilder.value": "value",
"connectorBuilder.addKeyValue": "Add",

"jobs.noAttemptsFailure": "Failed to start job.",

Expand Down

0 comments on commit df7dd51

Please sign in to comment.