Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🪟🎉 Connector builder authentication #20645

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect } from "react";

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";
Expand All @@ -29,7 +29,9 @@ function getView(selectedView: BuilderView) {
export const Builder: React.FC<BuilderProps> = ({ values, toggleYamlEditor }) => {
const { setBuilderFormValues, selectedView } = useConnectorBuilderState();
useEffect(() => {
setBuilderFormValues(values);
if (builderFormValidationSchema.isValidSync(values)) {
setBuilderFormValues(values);
}
}, [values, setBuilderFormValues]);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useField } from "formik";
import React, { useEffect } from "react";

import GroupControls from "components/GroupControls";
import { ControlLabels } from "components/LabeledControl";
import { DropDown } from "components/ui/DropDown";

interface Option {
label: string;
value: string;
default?: object;
}

interface OneOfOption {
label: string; // label shown in the dropdown menu
typeValue: string; // value to set on the `type` field for this component - should match the oneOf type definition
default?: object; // default values for the path
children?: React.ReactNode;
}

interface BuilderOneOfProps {
options: OneOfOption[];
path: string; // path to the oneOf component in the json schema
label: string;
tooltip: string;
}

export const BuilderOneOf: React.FC<BuilderOneOfProps> = ({ options, path, label, tooltip }) => {
const [, , oneOfPathHelpers] = useField(path);
const typePath = `${path}.type`;
const [typePathField, , typePathHelpers] = useField(typePath);
const value = typePathField.value ?? options[0].typeValue;

useEffect(() => {
typePathHelpers.setValue(value);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this at one point but later removed it from BuilderFields since I didn't think it was actually needed anymore. Could you double check if this is necessary to keep?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, we don't and I think it's better to truthfully show no selected option instead of defaulting to the first one as that wouldn't be reflected in the actual form state.


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

return (
<GroupControls
label={<ControlLabels label={label} infoTooltipContent={tooltip} />}
dropdown={
<DropDown
{...typePathField}
options={options.map((option) => {
return { label: option.label, value: option.typeValue, default: option.default };
})}
value={value ?? options[0].typeValue}
onChange={(selectedOption: Option) => {
if (selectedOption.value === value) {
return;
}
// clear all values for this oneOf and set selected option and default values
oneOfPathHelpers.setValue({
type: selectedOption.value,
...selectedOption.default,
});
}}
/>
}
>
{selectedOption?.children}
</GroupControls>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "services/connectorBuilder/ConnectorBuilderStateService";

import { DownloadYamlButton } from "../DownloadYamlButton";
import { BuilderFormValues } from "../types";
import { BuilderFormValues, getInferredInputs } from "../types";
import { useBuilderErrors } from "../useBuilderErrors";
import { AddStreamButton } from "./AddStreamButton";
import styles from "./BuilderSidebar.module.scss";
Expand Down Expand Up @@ -115,7 +115,10 @@ export const BuilderSidebar: React.FC<BuilderSidebarProps> = ({ className, toggl
onClick={() => handleViewSelect("inputs")}
>
<FontAwesomeIcon icon={faUser} />
<FormattedMessage id="connectorBuilder.userInputs" values={{ number: values.inputs.length }} />
<FormattedMessage
id="connectorBuilder.userInputs"
values={{ number: values.inputs.length + getInferredInputs(values).length }}
/>
</ViewSelectButton>

<div className={styles.streamsHeader}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { useIntl } from "react-intl";
import { BuilderCard } from "./BuilderCard";
import { BuilderConfigView } from "./BuilderConfigView";
import { BuilderField } from "./BuilderField";
import { BuilderOneOf } from "./BuilderOneOf";
import { BuilderTitle } from "./BuilderTitle";
import styles from "./GlobalConfigView.module.scss";
import { UserInputField } from "./UserInputField";

export const GlobalConfigView: React.FC = () => {
const { formatMessage } = useIntl();
Expand All @@ -16,6 +18,70 @@ export const GlobalConfigView: React.FC = () => {
<BuilderCard className={styles.content}>
<BuilderField type="string" path="global.urlBase" label="API URL" tooltip="Base URL of the source API" />
</BuilderCard>
<BuilderCard>
<BuilderOneOf
path="global.authenticator"
label="Authentication"
tooltip="Authentication method to use for requests send to the API"
flash1293 marked this conversation as resolved.
Show resolved Hide resolved
options={[
{ label: "No Auth", typeValue: "NoAuth" },
{
label: "API Key",
typeValue: "ApiKeyAuthenticator",
default: {
api_token: "{{ config['api_key'] }}",
},
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"
/>
</>
),
},
]}
/>
</BuilderCard>
</BuilderConfigView>
);
};
Loading