Skip to content

Commit 560cccf

Browse files
author
Joe Reuter
committed
Merge branch 'lmossman/connector-builder-pagination-slicing' into lmossman/connector-builder-stream-slicer
2 parents ab0a4c0 + 134fc17 commit 560cccf

File tree

10 files changed

+158
-115
lines changed

10 files changed

+158
-115
lines changed

airbyte-webapp/src/components/connectorBuilder/Builder/AuthenticationSection.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ export const AuthenticationSection: React.FC = () => {
1111
<BuilderOneOf
1212
path="global.authenticator"
1313
label="Authentication"
14-
tooltip="Authentication method to use for requests send to the API"
14+
tooltip="Authentication method to use for requests sent to the API"
1515
options={[
1616
{ label: "No Auth", typeValue: "NoAuth" },
1717
{
1818
label: "API Key",
1919
typeValue: "ApiKeyAuthenticator",
2020
default: {
2121
api_token: "{{ config['api_key'] }}",
22+
header: "",
2223
},
2324
children: (
2425
<>
@@ -70,6 +71,7 @@ export const AuthenticationSection: React.FC = () => {
7071
client_secret: "{{ config['client_secret'] }}",
7172
refresh_token: "{{ config['client_refresh_token'] }}",
7273
refresh_request_body: [],
74+
token_refresh_endpoint: "",
7375
},
7476
children: (
7577
<>
@@ -79,7 +81,7 @@ export const AuthenticationSection: React.FC = () => {
7981
label="Token refresh endpoint"
8082
tooltip="The URL to call to obtain a new access token"
8183
/>
82-
<UserInputField label="Client ID" tooltip="The OAuth client id" />
84+
<UserInputField label="Client ID" tooltip="The OAuth client ID" />
8385
<UserInputField label="Client secret" tooltip="The OAuth client secret" />
8486
<UserInputField label="Refresh token" tooltip="The OAuth refresh token" />
8587
<BuilderOptional>
@@ -88,7 +90,7 @@ export const AuthenticationSection: React.FC = () => {
8890
path="global.authenticator.scopes"
8991
optional
9092
label="Scopes"
91-
tooltip="Scopes to rquest"
93+
tooltip="Scopes to request"
9294
/>
9395
<BuilderField
9496
type="array"
@@ -141,7 +143,7 @@ export const AuthenticationSection: React.FC = () => {
141143
type="string"
142144
path="global.authenticator.header"
143145
label="Header"
144-
tooltip="Specific header of source for providing session token"
146+
tooltip="Specific HTTP header of source API for providing session token"
145147
/>
146148
<BuilderField
147149
type="string"
@@ -153,7 +155,7 @@ export const AuthenticationSection: React.FC = () => {
153155
type="string"
154156
path="global.authenticator.login_url"
155157
label="Login url"
156-
tooltip="Url fot getting a specific session token"
158+
tooltip="Url for getting a specific session token"
157159
/>
158160
<BuilderField
159161
type="string"

airbyte-webapp/src/components/connectorBuilder/Builder/Builder.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ function getView(selectedView: BuilderView) {
2929
export const Builder: React.FC<BuilderProps> = ({ values, toggleYamlEditor }) => {
3030
const { setBuilderFormValues, selectedView } = useConnectorBuilderState();
3131
useEffect(() => {
32-
if (builderFormValidationSchema.isValidSync(values)) {
33-
setBuilderFormValues(values);
34-
}
32+
setBuilderFormValues(values, builderFormValidationSchema.isValidSync(values));
3533
}, [values, setBuilderFormValues]);
3634

3735
return (

airbyte-webapp/src/components/connectorBuilder/Builder/BuilderField.tsx

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ interface BaseFieldProps {
3535
}
3636

3737
type BuilderFieldProps = BaseFieldProps &
38-
({ type: "string" | "integer" | "number" | "boolean" | "array" } | { type: "enum"; options: string[] });
38+
(
39+
| { type: "string" | "number" | "integer"; onChange?: (newValue: string) => void }
40+
| { type: "boolean"; onChange?: (newValue: boolean) => void }
41+
| { type: "array"; onChange?: (newValue: string[]) => void }
42+
| { type: "enum"; onChange?: (newValue: string) => void; options: string[] }
43+
);
3944

4045
const EnumField: React.FC<EnumFieldProps> = ({ options, value, setValue, error, ...props }) => {
4146
return (
@@ -80,21 +85,31 @@ export const BuilderField: React.FC<BuilderFieldProps> = ({
8085
);
8186
}
8287

88+
const setValue = (newValue: unknown) => {
89+
props.onChange?.(newValue as string & string[]);
90+
helpers.setValue(newValue);
91+
};
92+
8393
return (
8494
<ControlLabels className={styles.container} label={label} infoTooltipContent={tooltip} optional={optional}>
8595
{(props.type === "number" || props.type === "string" || props.type === "integer") && (
86-
<Input {...field} type={props.type} value={field.value ?? ""} error={hasError} readOnly={readOnly} />
96+
<Input
97+
{...field}
98+
onChange={(e) => {
99+
field.onChange(e);
100+
props.onChange?.(e.target.value);
101+
}}
102+
type={props.type}
103+
value={field.value ?? ""}
104+
error={hasError}
105+
readOnly={readOnly}
106+
/>
87107
)}
88108
{props.type === "array" && (
89-
<ArrayField name={path} value={field.value ?? []} setValue={helpers.setValue} error={hasError} />
109+
<ArrayField name={path} value={field.value ?? []} setValue={setValue} error={hasError} />
90110
)}
91111
{props.type === "enum" && (
92-
<EnumField
93-
options={props.options}
94-
value={field.value ?? props.options[0]}
95-
setValue={helpers.setValue}
96-
error={hasError}
97-
/>
112+
<EnumField options={props.options} value={field.value} setValue={setValue} error={hasError} />
98113
)}
99114
{hasError && (
100115
<Text className={styles.error}>

airbyte-webapp/src/components/connectorBuilder/Builder/BuilderOneOf.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useField } from "formik";
2-
import React, { useEffect } from "react";
2+
import React from "react";
33

44
import GroupControls from "components/GroupControls";
55
import { ControlLabels } from "components/LabeledControl";
@@ -28,12 +28,8 @@ interface BuilderOneOfProps {
2828
export const BuilderOneOf: React.FC<BuilderOneOfProps> = ({ options, path, label, tooltip }) => {
2929
const [, , oneOfPathHelpers] = useField(path);
3030
const typePath = `${path}.type`;
31-
const [typePathField, , typePathHelpers] = useField(typePath);
32-
const value = typePathField.value ?? options[0].typeValue;
33-
34-
useEffect(() => {
35-
typePathHelpers.setValue(value);
36-
}, []); // eslint-disable-line react-hooks/exhaustive-deps
31+
const [typePathField] = useField(typePath);
32+
const value = typePathField.value;
3733

3834
const selectedOption = options.find((option) => option.typeValue === value);
3935

airbyte-webapp/src/components/connectorBuilder/Builder/InputsView.module.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
.itemLabel {
2020
flex-grow: 1;
21-
font-size: 16px;
2221
}
2322

2423
.itemButton {

airbyte-webapp/src/components/connectorBuilder/Builder/InputsView.tsx

Lines changed: 79 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { faGear, faPlus } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
33
import { Form, Formik, useField, useFormikContext } from "formik";
44
import { JSONSchema7 } from "json-schema";
5-
import { useEffect, useMemo, useState } from "react";
5+
import { useMemo, useState } from "react";
66
import { FormattedMessage, useIntl } from "react-intl";
7+
import { useEffectOnce } from "react-use";
78
import * as yup from "yup";
89

910
import { Button } from "components/ui/Button";
@@ -97,8 +98,10 @@ export const InputsView: React.FC = () => {
9798
// make sure key can only occur once
9899
key: yup
99100
.string()
100-
.required("form.empty.error")
101-
.notOneOf(inputInEditing?.isNew ? usedKeys : usedKeys.filter((key) => key !== inputInEditing?.key)),
101+
.notOneOf(
102+
inputInEditing?.isNew ? usedKeys : usedKeys.filter((key) => key !== inputInEditing?.key),
103+
"connectorBuilder.duplicateFieldID"
104+
),
102105
required: yup.bool(),
103106
definition: yup.object().shape({
104107
title: yup.string().required("form.empty.error"),
@@ -116,36 +119,10 @@ export const InputsView: React.FC = () => {
116119
<Card withPadding className={styles.inputsCard}>
117120
<ol className={styles.list}>
118121
{inferredInputs.map((input) => (
119-
<li className={styles.listItem} key={input.key}>
120-
<div className={styles.itemLabel}>{input.definition.title || input.key}</div>
121-
<Button
122-
className={styles.itemButton}
123-
size="sm"
124-
variant="secondary"
125-
aria-label="Edit"
126-
onClick={() => {
127-
setInputInEditing(formInputToInputInEditing(input, true));
128-
}}
129-
>
130-
<FontAwesomeIcon className={styles.icon} icon={faGear} />
131-
</Button>
132-
</li>
122+
<InputItem input={input} setInputInEditing={setInputInEditing} isInferredInput />
133123
))}
134124
{inputs.value.map((input) => (
135-
<li className={styles.listItem} key={input.key}>
136-
<div className={styles.itemLabel}>{input.definition.title || input.key}</div>
137-
<Button
138-
className={styles.itemButton}
139-
size="sm"
140-
variant="secondary"
141-
aria-label="Edit"
142-
onClick={() => {
143-
setInputInEditing(formInputToInputInEditing(input, false));
144-
}}
145-
>
146-
<FontAwesomeIcon className={styles.icon} icon={faGear} />
147-
</Button>
148-
</li>
125+
<InputItem input={input} setInputInEditing={setInputInEditing} isInferredInput={false} />
149126
))}
150127
</ol>
151128
</Card>
@@ -197,6 +174,7 @@ export const InputsView: React.FC = () => {
197174
</BuilderConfigView>
198175
);
199176
};
177+
200178
const InputModal = ({
201179
inputInEditing,
202180
onClose,
@@ -207,14 +185,13 @@ const InputModal = ({
207185
onClose: () => void;
208186
}) => {
209187
const isInferredInputOverride = inputInEditing.isInferredInputOverride;
210-
const { isValid, values, setFieldValue } = useFormikContext<InputInEditing>();
188+
const { isValid, values, setFieldValue, setTouched } = useFormikContext<InputInEditing>();
189+
211190
const { formatMessage } = useIntl();
212-
const [title, titleMeta] = useField<string | undefined>("definition.title");
213-
useEffect(() => {
214-
if (titleMeta.touched && !isInferredInputOverride) {
215-
setFieldValue("key", sluggify(title.value || ""));
216-
}
217-
}, [setFieldValue, title.value, titleMeta.touched, isInferredInputOverride]);
191+
useEffectOnce(() => {
192+
// key input is always touched so errors are shown right away as it will be auto-set by the user changing the title
193+
setTouched({ key: true });
194+
});
218195

219196
return (
220197
<Modal
@@ -231,6 +208,11 @@ const InputModal = ({
231208
<BuilderField
232209
path="definition.title"
233210
type="string"
211+
onChange={(newValue) => {
212+
if (!isInferredInputOverride) {
213+
setFieldValue("key", sluggify(newValue || ""), true);
214+
}
215+
}}
234216
label={formatMessage({ id: "connectorBuilder.inputModal.inputName" })}
235217
tooltip={formatMessage({ id: "connectorBuilder.inputModal.inputNameTooltip" })}
236218
/>
@@ -242,7 +224,7 @@ const InputModal = ({
242224
tooltip={formatMessage(
243225
{ id: "connectorBuilder.inputModal.fieldIdTooltip" },
244226
{
245-
syntaxExample: "{{my_input}}",
227+
syntaxExample: `{{config['${values.key || "my_input"}']}}`,
246228
}
247229
)}
248230
/>
@@ -253,27 +235,26 @@ const InputModal = ({
253235
label={formatMessage({ id: "connectorBuilder.inputModal.description" })}
254236
tooltip={formatMessage({ id: "connectorBuilder.inputModal.descriptionTooltip" })}
255237
/>
256-
{values.type !== "unknown" ? (
238+
{values.type !== "unknown" && !isInferredInputOverride ? (
257239
<>
258-
{!isInferredInputOverride && (
259-
<>
260-
<BuilderField
261-
path="type"
262-
type="enum"
263-
options={["string", "number", "integer", "array", "boolean", "enum"]}
264-
label={formatMessage({ id: "connectorBuilder.inputModal.type" })}
265-
tooltip={formatMessage({ id: "connectorBuilder.inputModal.typeTooltip" })}
266-
/>
267-
{values.type === "enum" && (
268-
<BuilderField
269-
path="definition.enum"
270-
type="array"
271-
optional
272-
label={formatMessage({ id: "connectorBuilder.inputModal.enum" })}
273-
tooltip={formatMessage({ id: "connectorBuilder.inputModal.enumTooltip" })}
274-
/>
275-
)}
276-
</>
240+
<BuilderField
241+
path="type"
242+
type="enum"
243+
options={["string", "number", "integer", "array", "boolean", "enum"]}
244+
onChange={() => {
245+
setFieldValue("definition.default", undefined);
246+
}}
247+
label={formatMessage({ id: "connectorBuilder.inputModal.type" })}
248+
tooltip={formatMessage({ id: "connectorBuilder.inputModal.typeTooltip" })}
249+
/>
250+
{values.type === "enum" && (
251+
<BuilderField
252+
path="definition.enum"
253+
type="array"
254+
optional
255+
label={formatMessage({ id: "connectorBuilder.inputModal.enum" })}
256+
tooltip={formatMessage({ id: "connectorBuilder.inputModal.enumTooltip" })}
257+
/>
277258
)}
278259
<BuilderField
279260
path="definition.airbyte_secret"
@@ -282,15 +263,13 @@ const InputModal = ({
282263
label={formatMessage({ id: "connectorBuilder.inputModal.secret" })}
283264
tooltip={formatMessage({ id: "connectorBuilder.inputModal.secretTooltip" })}
284265
/>
285-
{!isInferredInputOverride && (
286-
<BuilderField
287-
path="required"
288-
type="boolean"
289-
optional
290-
label={formatMessage({ id: "connectorBuilder.inputModal.required" })}
291-
tooltip={formatMessage({ id: "connectorBuilder.inputModal.requiredTooltip" })}
292-
/>
293-
)}
266+
<BuilderField
267+
path="required"
268+
type="boolean"
269+
optional
270+
label={formatMessage({ id: "connectorBuilder.inputModal.required" })}
271+
tooltip={formatMessage({ id: "connectorBuilder.inputModal.requiredTooltip" })}
272+
/>
294273
<BuilderField
295274
path="showDefaultValueField"
296275
type="boolean"
@@ -317,7 +296,11 @@ const InputModal = ({
317296
</>
318297
) : (
319298
<InfoBox>
320-
<FormattedMessage id="connectorBuilder.inputModal.unsupportedInput" />
299+
{isInferredInputOverride ? (
300+
<FormattedMessage id="connectorBuilder.inputModal.inferredInputMessage" />
301+
) : (
302+
<FormattedMessage id="connectorBuilder.inputModal.unsupportedInput" />
303+
)}
321304
</InfoBox>
322305
)}
323306
</ModalBody>
@@ -340,3 +323,30 @@ const InputModal = ({
340323
</Modal>
341324
);
342325
};
326+
327+
const InputItem = ({
328+
input,
329+
setInputInEditing,
330+
isInferredInput,
331+
}: {
332+
input: BuilderFormInput;
333+
setInputInEditing: (inputInEditing: InputInEditing) => void;
334+
isInferredInput: boolean;
335+
}): JSX.Element => {
336+
return (
337+
<li className={styles.listItem} key={input.key}>
338+
<div className={styles.itemLabel}>{input.definition.title || input.key}</div>
339+
<Button
340+
className={styles.itemButton}
341+
size="sm"
342+
variant="secondary"
343+
aria-label="Edit"
344+
onClick={() => {
345+
setInputInEditing(formInputToInputInEditing(input, isInferredInput));
346+
}}
347+
>
348+
<FontAwesomeIcon className={styles.icon} icon={faGear} />
349+
</Button>
350+
</li>
351+
);
352+
};

0 commit comments

Comments
 (0)