Skip to content

Commit

Permalink
🪟🎉 Connector builder: Allow defining inputs (#20431)
Browse files Browse the repository at this point in the history
* move connector builder components into the same shared components/connectorBuilder directory

* move diff over from poc branch

* save current progress

* add modal for adding streams

* focus stream after adding and reset button style

* add reset confirm modal and select view on add

* style global config and streams buttons

* styling improvements

* handle long stream names better

* pull in connector manifest schema directly

* add box shadows to resizable panels

* upgrade orval and use connector manifest schema directly

* remove airbyte protocol from connector builder api spec

* generate python models from openapi change

* fix position of yaml toggle

* handle no stream case with better looking message

* group global fields into single object and fix console error

* confirmation modal on toggling dirty form + cleanup

* fix connector name display

* undo change to manifest schema

* remove commented code

* remove unnecessary change

* fix spacing

* use shadow mixin for connector img

* add comment about connector img

* change onSubmit to no-op

* remove console log

* clean up styling

* simplify sidebar to remove StreamSelectButton component

* swap colors of toggle

* move FormikPatch to src/core/form

* move types up to connectorBuilder/ level

* use grid display for ui yaml toggle button

* use spread instead of setting array index directly

* add intl in missing places

* pull connector manifest schema in through separate openapi spec

* use correct intl string id

* throttle setting json manifest in yaml editor

* use  button prop instead of manually styling

* consolidate AddStreamButton styles

* fix sidebar flex styles

* use specific flex properties instead of flex

* clean up download and reset button styles

* use row-reverse for yaml editor download button

* fix stream selector styles to remove margins

* give connector setup guide panel same corner and shadow styles

* remove blur from page display

* set view to stream when selected in test panel

* add placeholder when stream name is empty

* switch to index-based stream selection to preserve testing panel selected stream on rename

* handle empty name in stream selector

* make connector form work in connector builder

* wip

* fix small stuff

* add basic input UI

* user inputs

* make most of inputs configuration work

* fix a bunch of stuff

* handle unknown config types

* add warning label

* fix label

* fix some styling

* review comments

* improve state management and error handling

* handle stored form values that don't contain new fields properly

* Update airbyte-webapp/src/locales/en.json

Co-authored-by: Lake Mossman <lake@airbyte.io>

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

Co-authored-by: Lake Mossman <lake@airbyte.io>

* inputs editing weirdness

* input form reset

* using the Label component

* 🪟🎉 Connector builder authentication (#20645)

* allow auth configuration

* check for conflicts with the inferred inputs

* fix invisible inputs

* reduce redundancy and hide advanced input options for inferred inputs

* unnecessary validation

* typo

* unnecessary effect hook

* build spec even for invalid forms but do not update stream list

* fix keys

* 🪟🎉 Connector builder: Session token and oauth authentication (#20712)

* session token and oauth authentication

* fill in session token variable

* typos

* make sure validation error does not go away

* 🪟🎉 Connector builder: Always validate inputs form (#20664)

* validate user input outside of form

* review comments

Co-authored-by: lmossman <lake@airbyte.io>

Co-authored-by: lmossman <lake@airbyte.io>
  • Loading branch information
Joe Reuter and lmossman authored Dec 22, 2022
1 parent 200cfe7 commit c7fd310
Show file tree
Hide file tree
Showing 31 changed files with 1,321 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ export const AddStreamButton: React.FC<AddStreamButtonProps> = ({ onAddStream, b
<ModalBody className={styles.body}>
<BuilderField
path="streamName"
type="text"
type="string"
label={formatMessage({ id: "connectorBuilder.addStreamModal.streamNameLabel" })}
tooltip={formatMessage({ id: "connectorBuilder.addStreamModal.streamNameTooltip" })}
/>
<BuilderField
path="urlPath"
type="text"
type="string"
label={formatMessage({ id: "connectorBuilder.addStreamModal.urlPathLabel" })}
tooltip={formatMessage({ id: "connectorBuilder.addStreamModal.urlPathTooltip" })}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { BuilderCard } from "./BuilderCard";
import { BuilderField } from "./BuilderField";
import { BuilderOneOf } from "./BuilderOneOf";
import { BuilderOptional } from "./BuilderOptional";
import { KeyValueListField } from "./KeyValueListField";
import { UserInputField } from "./UserInputField";

export const AuthenticationSection: React.FC = () => {
return (
<BuilderCard>
<BuilderOneOf
path="global.authenticator"
label="Authentication"
tooltip="Authentication method to use for requests sent to the API"
options={[
{ label: "No Auth", typeValue: "NoAuth" },
{
label: "API Key",
typeValue: "ApiKeyAuthenticator",
default: {
api_token: "{{ config['api_key'] }}",
header: "",
},
children: (
<>
<BuilderField
type="string"
path="global.authenticator.header"
label="Header"
tooltip="HTTP header which should be set to the API Key"
/>
<UserInputField
label="API Key"
tooltip="The API key issued by the service. Fill it in in the user inputs"
/>
</>
),
},
{
label: "Bearer",
typeValue: "BearerAuthenticator",
default: {
api_token: "{{ config['api_key'] }}",
},
children: (
<UserInputField
label="API Key"
tooltip="The API key issued by the service. Fill it in in the user inputs"
/>
),
},
{
label: "Basic HTTP",
typeValue: "BasicHttpAuthenticator",
default: {
username: "{{ config['username'] }}",
password: "{{ config['password'] }}",
},
children: (
<>
<UserInputField label="Username" tooltip="The username for the login. Fill it in in the user inputs" />
<UserInputField label="Password" tooltip="The password for the login. Fill it in in the user inputs" />
</>
),
},
{
label: "OAuth",
typeValue: "OAuthAuthenticator",
default: {
client_id: "{{ config['client_id'] }}",
client_secret: "{{ config['client_secret'] }}",
refresh_token: "{{ config['client_refresh_token'] }}",
refresh_request_body: [],
token_refresh_endpoint: "",
},
children: (
<>
<BuilderField
type="string"
path="global.authenticator.token_refresh_endpoint"
label="Token refresh endpoint"
tooltip="The URL to call to obtain a new access token"
/>
<UserInputField label="Client ID" tooltip="The OAuth client ID" />
<UserInputField label="Client secret" tooltip="The OAuth client secret" />
<UserInputField label="Refresh token" tooltip="The OAuth refresh token" />
<BuilderOptional>
<BuilderField
type="array"
path="global.authenticator.scopes"
optional
label="Scopes"
tooltip="Scopes to request"
/>
<BuilderField
type="array"
path="global.authenticator.token_expiry_date_format"
optional
label="Token expiry date format"
tooltip="The format of the expiry date of the access token as obtained from the refresh endpoint"
/>
<BuilderField
type="string"
path="global.authenticator.expires_in_name"
optional
label="Token expiry property name"
tooltip="The name of the property which contains the token exipiry date in the response from the token refresh endpoint"
/>
<BuilderField
type="string"
path="global.authenticator.access_token_name"
optional
label="Access token property name"
tooltip="The name of the property which contains the access token in the response from the token refresh endpoint"
/>
<BuilderField
type="string"
path="global.authenticator.grant_type"
optional
label="Grant type"
tooltip="The grant type to request for access_token"
/>
<KeyValueListField
path="global.authenticator.refresh_request_body"
label="Request Parameters"
tooltip="The request body to send in the refresh request"
/>
</BuilderOptional>
</>
),
},
{
label: "Session token",
typeValue: "SessionTokenAuthenticator",
default: {
username: "{{ config['username'] }}",
password: "{{ config['password'] }}",
session_token: "{{ config['session_token'] }}",
},
children: (
<>
<BuilderField
type="string"
path="global.authenticator.header"
label="Header"
tooltip="Specific HTTP header of source API for providing session token"
/>
<BuilderField
type="string"
path="global.authenticator.session_token_response_key"
label="Session token response key"
tooltip="Key for retrieving session token from api response"
/>
<BuilderField
type="string"
path="global.authenticator.login_url"
label="Login url"
tooltip="Url for getting a specific session token"
/>
<BuilderField
type="string"
path="global.authenticator.validate_session_url"
label="Validate session url"
tooltip="Url to validate passed session token"
/>
<UserInputField label="Username" tooltip="The username" />
<UserInputField label="Password" tooltip="The password" />
<UserInputField
label="Session token"
tooltip="Session token generated by user (if provided username and password are not required)"
/>
</>
),
},
]}
/>
</BuilderCard>
);
};
22 changes: 16 additions & 6 deletions airbyte-webapp/src/components/connectorBuilder/Builder/Builder.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import { Form } from "formik";
import { useEffect } from "react";

import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService";
import { BuilderView, useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService";

import { BuilderFormValues } from "../types";
import { builderFormValidationSchema, BuilderFormValues } from "../types";
import styles from "./Builder.module.scss";
import { BuilderSidebar } from "./BuilderSidebar";
import { GlobalConfigView } from "./GlobalConfigView";
import { InputsView } from "./InputsView";
import { StreamConfigView } from "./StreamConfigView";

interface BuilderProps {
values: BuilderFormValues;
toggleYamlEditor: () => void;
}

function getView(selectedView: BuilderView) {
switch (selectedView) {
case "global":
return <GlobalConfigView />;
case "inputs":
return <InputsView />;
default:
return <StreamConfigView streamNum={selectedView} />;
}
}

export const Builder: React.FC<BuilderProps> = ({ values, toggleYamlEditor }) => {
const { setBuilderFormValues, selectedView } = useConnectorBuilderState();
useEffect(() => {
setBuilderFormValues(values);
setBuilderFormValues(values, builderFormValidationSchema.isValidSync(values));
}, [values, setBuilderFormValues]);

return (
<div className={styles.container}>
<BuilderSidebar className={styles.sidebar} toggleYamlEditor={toggleYamlEditor} />
<Form className={styles.form}>
{selectedView === "global" ? <GlobalConfigView /> : <StreamConfigView streamNum={selectedView} />}
</Form>
<Form className={styles.form}>{getView(selectedView)}</Form>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { useField } from "formik";
import { FormattedMessage } from "react-intl";

import { ControlLabels } from "components/LabeledControl";
import { LabeledSwitch } from "components/LabeledSwitch";
import { DropDown } from "components/ui/DropDown";
import { Input } from "components/ui/Input";
import { TagInput } from "components/ui/TagInput";
import { Text } from "components/ui/Text";
import { InfoTooltip } from "components/ui/Tooltip/InfoTooltip";

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

Expand All @@ -28,10 +30,17 @@ interface BaseFieldProps {
path: string;
label: string;
tooltip?: string;
readOnly?: boolean;
optional?: boolean;
}

type BuilderFieldProps = BaseFieldProps & ({ type: "text" | "array" } | { type: "enum"; options: string[] });
type BuilderFieldProps = BaseFieldProps &
(
| { type: "string" | "number" | "integer"; onChange?: (newValue: string) => void }
| { type: "boolean"; onChange?: (newValue: boolean) => void }
| { type: "array"; onChange?: (newValue: string[]) => void }
| { type: "enum"; onChange?: (newValue: string) => void; options: string[] }
);

const EnumField: React.FC<EnumFieldProps> = ({ options, value, setValue, error, ...props }) => {
return (
Expand All @@ -51,23 +60,56 @@ const ArrayField: React.FC<ArrayFieldProps> = ({ name, value, setValue, error })
return <TagInput name={name} fieldValue={value} onChange={(value) => setValue(value)} error={error} />;
};

export const BuilderField: React.FC<BuilderFieldProps> = ({ path, label, tooltip, optional = false, ...props }) => {
export const BuilderField: React.FC<BuilderFieldProps> = ({
path,
label,
tooltip,
optional = false,
readOnly,
...props
}) => {
const [field, meta, helpers] = useField(path);
const hasError = !!meta.error && meta.touched;

if (props.type === "boolean") {
return (
<LabeledSwitch
{...field}
checked={field.value}
label={
<>
{label} {tooltip && <InfoTooltip placement="top-start">{tooltip}</InfoTooltip>}
</>
}
/>
);
}

const setValue = (newValue: unknown) => {
props.onChange?.(newValue as string & string[]);
helpers.setValue(newValue);
};

return (
<ControlLabels className={styles.container} label={label} infoTooltipContent={tooltip} optional={optional}>
{props.type === "text" && <Input {...field} type={props.type} value={field.value ?? ""} error={hasError} />}
{(props.type === "number" || props.type === "string" || props.type === "integer") && (
<Input
{...field}
onChange={(e) => {
field.onChange(e);
props.onChange?.(e.target.value);
}}
type={props.type}
value={field.value ?? ""}
error={hasError}
readOnly={readOnly}
/>
)}
{props.type === "array" && (
<ArrayField name={path} value={field.value ?? []} setValue={helpers.setValue} error={hasError} />
<ArrayField name={path} value={field.value ?? []} setValue={setValue} error={hasError} />
)}
{props.type === "enum" && (
<EnumField
options={props.options}
value={field.value ?? props.options[0]}
setValue={helpers.setValue}
error={hasError}
/>
<EnumField options={props.options} value={field.value} setValue={setValue} error={hasError} />
)}
{hasError && (
<Text className={styles.error}>
Expand Down
Loading

0 comments on commit c7fd310

Please sign in to comment.